10
\$\begingroup\$

So I was thinking about how monolithic my classes get a lot of the time. For example, in the Character class's Jump method, one may have a reference to a sound effect object and play that. By itself that is fine but when physics, animation, collision, etc. are taken into account, the Jump method gets huge and the Character class has a lot of dependencies to many different things. Still, this may be fine. However, what if we no longer want a sound to play when the character jumps? Now, we have to find that specific line of code in the jumbled mess of the Jump code and comment it out or whatever.

So.. I was thinking..

What if, instead, there was some sort of AudioSystem class and all it did was subscribe to random events it's interested in in other classes. For example, the Character class may have a Jumped event (static too, I suppose) that is raised within the Character class in the method. Then, the Character class would know nothing about the little sound effect that is played when the character jumps. The AudioSystem would just be a huge class that the programmer could retreat to to hook up sound effects with certain events that happen in the game through the use of static events. Then, if it got too big it could be separated in to subclasses like EffectsAudioSystem, BackgroundAudioSystem, AmbientAudioSystem, et cetera.

Then, in the options for the game, one could have a checkbox to enable or disable these sorts of sounds and all that would need to be done is just to disable that one system with a simple and single Boolean flag. This idea of systems could also be extended in to things like physics, animations, etc. to the point where most of the game responses resulting from player actions are hooked up through these elaborate and decoupled systems.

Okay, so my question may be a little vague, but how does this sort of thing sound? I haven't really ever heard of much kind of talk about this sort of system. This is all up in my head right now without any coding done so far so perhaps it's one of those "good in theory but not in practice" kind of deals. Would this kind of system work with a larger game or would it eventually break down and become even more of a spaghetti mess than the original system?

\$\endgroup\$
2
  • 5
    \$\begingroup\$ Sounds like a sound idea :) (On a more serious note: Using messages for communication between loosely coupled subsystems/classes is generally a good idea design-wise) \$\endgroup\$
    – bummzack
    Feb 14, 2012 at 22:32
  • 1
    \$\begingroup\$ this is how it's done, you should put your rendering in a separate class aswell, if you haven't already (as in, you shouldn't have a draw() function in the Character class). \$\endgroup\$
    – dreta
    Feb 15, 2012 at 2:46

3 Answers 3

2
\$\begingroup\$

Messages are a hell to debug and maintain. It sounds good in theory, but once put in practice it gets messy with lots of duplicated data sending around. The jump-sound-effect will need a lot more data at the end, for instance the position, velocity, material the character is on, you name it, the list will be long at the end.

So either you will need to collect this data and send it to the AudioManager via a very specific event/message with the data copied in it or you will send a reference to the character in the message, so the AudioManager can access the data, both ways end up messy, and now the audio-manager has to choose a sound for the material-underground etc.

So at the end the specific event (which is a very specific class for only this message) will couple those classes very deep again. Not much won and at the end you will have a messy big list of very specific events/classes which only serve the purpose of sending data around, which exists already, and may be out of date and will suffer from all other problems of duplicated data.

So there will be a huge list of unnecessary classes to maintain which introduce a deep coupling between the character and the AudioManager, except now it is scattered all over the source-code. Not only in the Character- and AudioManager-classes.

Still a good idea to decouple your code, but Messages are really just another way of deep couplings. Some code just has to be coupled, use the most direct way to couple them, don't over-engineer.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ How is a well designed messaging system "just another way of deep couplings"? Sending messages is the absolute minimum coupling without the objects never communicating. The way you have it designed might cause issues but if the system only takes in a sound, location, and type it solves all of his problems and introduces none of the ones you suggest might occur. The audio system shouldn't calculate what sound is needed it should abstract away all of the settings and preferably diffusion issues. en.wikipedia.org/wiki/Coupling_(computer_programming) \$\endgroup\$ Feb 15, 2012 at 16:57
  • 1
    \$\begingroup\$ @ClassicThunder the point is in practice this approach does not scale very well. It works good for simple applications and as long as all you need is a general PlaySoundEvent. But the question is about the AudioManager listening to a specialized OnJump()-event, so the character can get rid of the audio-work. This however will not be the case with a simple PlaySoundEvent since the character has to choose the sound and send it to the AudioManager, which invalidates the original point of introducing the OnJumpEvent to get rid of the audio work. \$\endgroup\$ Feb 15, 2012 at 17:40
  • 1
    \$\begingroup\$ When using the OnJumpEvent however, you can choose either adding the reference to the character to the event or copying all important data from the character into the event. Of course you are right, the latter would not introduce a deep coupling, but it will suffer from data duplication problems and a new data-passing object which has to be maintained, like for all other new events. \$\endgroup\$ Feb 15, 2012 at 17:42
  • 1
    \$\begingroup\$ -1 cause while I agree that having messages so specialized (OnJump) is likely a bad idea, this is just a long 'No this is a bad idea' instead of useful information to get the person to make a PlaySound event that takes the name of a sound effect and a 3D position and/or volume that it occurred at. \$\endgroup\$
    – James
    Feb 15, 2012 at 18:26
  • \$\begingroup\$ @James thanks for the input, I didn't mean to say use a PlaySound event, I meant to say events are a nice abstraction for a GUI-database application, but not practical for a complex game. \$\endgroup\$ Feb 15, 2012 at 18:37
2
\$\begingroup\$

I don't think a message passing system is over engineering at all. In fact it can make it dramatically easier to get things done in the polish phase. You're doing it right!

What you described is exactly what I threw together for our Global Game Jam game last year. I was responsible for creating and editing the SFX, and integrating music that myself and another composer wrote into the game in a way that didn't suck.

What's great about this approach from an audio perspective is that it allows you to do so many more interesting things with your sound. If you think a sound effect in a game is merely a sound file, volume, and panning, then you're doing it wrong.

Example

For our game you were a dinosaur flying a spaceship running into planets to score points. We were working in Flash, so a data driven infrastructure wasn't needed. The AudioManager was a class consisting of a bunch of static methods whose sole purpose was to control what sounds happened in response to a game event.

If I were to write it in C++, then it would have taken a bit more time to abstract all the possible behaviors sounds could have. The requirements for a message notifying the system an action had taken place wouldn't be too complicated. It would just need the message type, the object of origin or object being affected, access to some kind of game state context, and not much else. The protocol could grow as the game's needs grow. Naturally, if you do all of this in implementation in code (like our shoddy GGJ code), you have a worse monolithic class problem. But that's easily mitigated by making a data-driven system.

Anyway, here's how our game audio system reacted to various messages:

  • Player collides with planet: This would trigger a planet explosion sound, basic enough. then immediately after it would query the running combo counter. If it was high enough it would schedule a sound effect to be played half a second or so later of the dinosaur doing a victory roar. Also in the background a random planet population value was calculated (something like 600 to 3000--I have no idea why that range was chosen, it was some abandoned gameplay mechanic and still lying around for me to use to make the audio interesting), and so I used that to scale the volume of the distant sound of screams (planetary citizens meeting an untimely fate).

  • Player holds spacebar for acceleration: Upon receiving this a little "whoosh" thruster sound was played, but also simultaneously a low looped engine roar ramped up over 1.5 seconds. The particle system also used this to fire an emitter IIRC

  • Player lets go of spacebar for deceleration: Now that the player had let go of the spacebar, the audio system knew it had to ramp the engine loop back down. If I had more time I would have liked to layer another sound over it that was a sort of whining pitching down type of sound.

  • Player collides with evil space mine: Space mines are bad, so not only is there a metallic impact sound combined with an explosion (that's just baked into one sound), but there's also a randomly selected dinosaur dismay sound that plays. It's more likely to choose more "crying" like sounds as the player's health gets lower.

An already fun game becomes a delight to play when its soundtrack is active and dynamic, even with just little simple behaviors like I described above. Yes there are some logistics to work out with making sure the correct data gets passed. But hey, BFD. It will be far from the most complicated thing you have to write in the larger scope of the game code.

In fact, FMOD and Wwise work like this. They don't have a central message dispatcher, but you effectively post events to their central systems and they react by playing a sound effect that was pre-designed by an audio implementor in an authoring tool. Think of it like giving your game a live DJ. He sits and watches what's going on, and triggers sound clips at the right times to keep things interesting, mixing them so they fit well into the preexisting audio environment.

[EDIT] Also, I see you've tagged this C#. Is this XNA, and if so are you using XACT? If you are using XNA you should be using XACT.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ This might work for a small project, it might even be fun. However in a big one you end up with a huge number of message classes, that need to be maintained, whereas a simple function-call would have had the same effect. That is why the event system does not scale well, it gets hard to manage the bigger the project gets. \$\endgroup\$ Feb 15, 2012 at 18:31
  • 1
    \$\begingroup\$ Btw we work with FMOD at my studio, there is no message/event-system, you don't send events to FMOD, you simply call a c-function or a c++ method to play something. They just call their sounds "events", that does not make it an events system, its just the term they use instead of sound. \$\endgroup\$ Feb 15, 2012 at 18:32
  • \$\begingroup\$ No, why? You just call the function directly instead of using an event to pass the parameters. An event is at the end nothing else as a function call, just passing the parameters in the event object, instead of passing them directly. The only difference is the new indirection introduced by the event system, but at the end its a simple function call, only unnecessarily over-complicated. \$\endgroup\$ Feb 15, 2012 at 18:46
  • \$\begingroup\$ @MaikSemder How do method calls not end up in their own tangled web of nastiness? Also, I tried to make note of that distinction between an event system and the "events" used by Wwise and FMOD. The idea I'm getting at is that complex audio logic does not belong in game object classes, and abstracting away the sound logic such that the interface is akin to dispatching an event makes it easier to have rich audio logic. I really see little functional difference between EventManager->dispatch("Sound:PlayerJump") and soundSystem->playFMODEvent("/MyGame/Player/Jump"). \$\endgroup\$ Feb 15, 2012 at 18:49
  • 2
    \$\begingroup\$ There might be the benefit of easier coding, but it is not for free, it comes with performance-, maintainance- and harder-debugging-costs. My point is, the benefit is not worth it for big projects. You are dealing with way more objects than without events and have to pay the price for it. The only place I would consider using a message system is for inter-thread communication to prevent locking between threads. \$\endgroup\$ Feb 15, 2012 at 19:07
0
\$\begingroup\$

I agree with Maik Semder, that a message passing system may be over-engineering (for now anyway).

From what I understand, your class currently looks like the Bjorn "monolithic class" as can be seen in "A monolithic class" here.

I suggest you read that article, and, although a full component system would be overkill for now, if you read to "Splitting out the rest", that should give you a good way to abstract your behaviors and eventually maybe move to a more complex solution. It will give you a good base to start from anyway.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .