1
\$\begingroup\$

It does not seem like a good idea to include this functionality in the game logic like that even if the concrete implementation of the sound or graphics effect is abstracted away. Ideally, the game logic/collision logic should not know anything about such things at all:

class ProjectilesSystem
{
  Action<string> makeSound;
  public ProjectilesSystem(Action<string> makeSound) 
  { 
    this.makeSound=makeSound;
  }
  void Update(ProjecttileComponent p)
  {
     if (p.Hit()) makeSound(p.GetSoundName());
  }
}

So we would need a separate systems for sounds/effects. But because there are lots of components which can make sounds under lots of circumstances theoretically I would need a ProjectileSoundSystem, ProjectileParticleEffectsSystem, VehicleSoundSystem, VehicleParticleEffectsSystem, AnimalSoundSystem, AnimalParticleEffectsSystem.. (you got the idea), would this be practical?

What are the alternatives?

Maybe some kind of event system so that component systems can publish what the component is doing right now:

  void Update(ProjecttileComponent p)
  {
     if (p.Hit()) eventSystem.Fire("ProjectileHit");
  }

In this approach, how can the consument know how the ProjectileHit should sound like? I mean depending on the projectile the impact can be a different sound. So each entity would need a map for eventType=>SoundName and similar maps for other stuff like effects and so on. Does this make sense? Are there other solutions?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Potentially related: How to do State-Based Animation with ECS. As you say, this is usually handled with some form of The Observer Pattern (ie. events or message queues). The projectile entity could have a CollisionSound component attached which subscribes to Collision events on that object, and contains data about what sound to play. A CollisionEventSystem could iterate over all such collision event subscribers for each collision that occurs, and invoke them. \$\endgroup\$
    – DMGregory
    Commented Mar 8, 2022 at 18:43

1 Answer 1

2
\$\begingroup\$

I'll just answer from the perspective of sound integration here. Not quite sure what all would be included in the graphics side of your cross-cutting concerns.

I've seen these types of cross-cutting sound integrations take a few different shapes, generally. Here's a breakdown on how I see the pros/cons of each approach.

Singleton Sound Manager

A globally available, "master mix" style sound manager that receives calls from various systems / Update() methods directly via SoundManager.Trigger(soundId).

Pros

  • Initially easy to reason about
  • All things sound related are here except the triggers; it's centralized and easily modified in broad strokes - assuming a consistent API in consuming code
  • Likely higher performance w/r/t fewer disparate sound systems being instanced all at once

Cons

  • Dangerously easy to bloat with complexity (voice counts, choke groups, not to mention larger sound integration concerns like occlusion, dynamic music, etc.)
  • Likely to evolve into a number of more specialized [Foo]SoundManager singletons, which need to be provided more context as external systems request sound playback (or cancellation, or looping, etc.)
  • Consuming classes become more complex as a result

Sounds in Systems

A list of sounds that can be triggered by a system is stored close to the system and used internally. Some form of ad-hoc configuration might accompany the list of sounds, but it is otherwise fairly fire-and-forget.

Pros

  • Easy to reason about, even at scale (bloat notwithstanding)
  • Sound assets are assigned, managed, and triggered close to where they are used

+/-

  • As you mentioned re: collision, suddenly your CollisionManager isn't just physics; it's now concerned with sound too.
  • Some technical sound designers would argue that this is A Good Thing, as it allows for more nuanced sound integration within each system (e.g. different sounds are triggered based on impact velocity, insert effects are tweaked based on collision state.)

Cons

  • Systems become larger in scope, potentially resulting in more abstraction: CollisionManager becomes the tip of an architecture subtree with CollisionSoundManager managing sound with knowledge of state provided by separate CollisionPhysicsManager, and so on
  • Cross cutting concerns (e.g. voice count, choke groups, etc.) are either repeated (bad) or broken into more disparate sound subsystems (e.g. SoundPlaybackManager) which sort of just leads you back to the Singleton Sound Manager approach
  • Without proper abstraction, systems needing sound can get messy, quickly

Event Sourcing / SoundEventManager

Essentially every notable event in your game becomes logged to some central EventStore. This may already be in place for save systems or other databasing needs, but this can be an extremely powerful well from which a SoundEventManager can parse events, decide if they warrant a sound, infer context from surrounding events, and trigger + manage sounds appropriately.

Pros

  • Lots of power from a centralized place, though proper abstraction of sound playback concerns is absolutely necessary.
  • Enables adding, modifying, or removing sounds long after other systems are created without modifying said systems.

+/-

  • Rather than relying on systems to trigger a sound themselves (Sounds in Systems), or request playback from an orchestrator (Singleton Sound Manager), the systems just publish events with reckless abandon.
  • Can go well if the core EventStore makes intelligent use of debounces / throttles and otherwise keeps it's memory footprint in check via streaming to disk as needed.
  • But it can also go poorly, with far too many useless event records and a monstrous memory footprint to boot.

Cons

  • Absolutely the more complex of the architectures to get right.
  • Aside from EventStore hygiene gotchas mentioned above, latency can be an issue if the event queue isn't processed on nearly every tick.

Personal Opinion

I'm a big fan of the Event Sourcing approach because I believe it lends itself to good design in other areas of gameplay, despite the complexity and need to carefully manage + persist incoming events. Having centralized communication is ultimately always needed for systems to cooperate and doing so via event logs is a tried-and-true approach.

All that said, I won't hesitate to recommend mature sound integration platforms like WWise or FMOD. Sound integration is such a deep and nuanced practice that tooling around it has become very full-featured and truly helps lighten the load as a [solo] developer just trying to get work done.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I now tried the event sourcing approach (effectively what I meant with my last described approach) and I think this is a good architecture. \$\endgroup\$
    – codymanix
    Commented Mar 13, 2022 at 23:14
  • \$\begingroup\$ Glad to hear it @codymanix! Feel free to DM me a link if you ever need an extra beta tester :) \$\endgroup\$
    – aransley
    Commented Mar 16, 2022 at 19:10

You must log in to answer this question.

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