Sound Component - UQcsse3200/2023-studio-2 GitHub Wiki

First see Components.

The SoundComponent is a custom component used to manage entity sounds through an event driven system. This component does not do anything by itself. It instead loads the sounds defined within the given SoundsConfig and begins listening for events to interact with these sounds through the entities EventHandler.

When to use it

Use this component when you would like sounds to be conditional upon an action performed by an entity (e.g. player movement, enemy death, grenade exploding, button clicked, etc).

When not to use it

Do not use this component for game sounds which are not conditional upon an action performed by an entity (e.g. game music, ambient sounds such as birds chirping, etc). For these use cases, you should instead instantiate and use sounds within the GameArea itself.

Usage

Initialisation

To initialise the SoundComponent, you must provide a SoundsConfig class as its only parameter which stores an ObjectMap of sound names and their respective file locations. Sounds cannot be added after initialisation, so the SoundsConfig given must include all the sounds that you wish to use throughout the lifetime of the entity. Please see the SoundsConfig section for more information surrounding how to setup and ideally load the SoundsConfig from a JSON file.

Here is an example of how to create initialise the SoundComponent and add it to an entity. For this example, the SoundsConfig will be programmatically set, however it is recommended that you load this from a customisable JSON file for actual use.

SoundsConfig config = new SoundsConfig();

// Add some sample sounds. The files specified in the config must be
// already loaded via the ResourceService. Failure to do so will result
// in the sound failing to load.
config.soundsMap.put("sound_1", "sounds/sound_file_1.wav");
config.soundsMap.put("sound_2", "sounds/sound_file_2.wav");
config.soundsMap.put("sound_3", "sounds/sound_file_3.wav");

// intialise component
SoundComponent component = new SoundComponent(config);

Entity entity = new Entity();

// add component to entity
entity.addComponent(component);

The sound component will now be added to the entity and will listen for play, loop, and stop events triggered through the entities EventHandler.

Triggering Sounds

Once you have added the SoundComponent to the entity as described, you can trigger the registered sounds via the entities EventHandler. This can be retrieved via the entities getEvents() method.

The available events are playSound, loopSound, and stopSound. The event must be called with one parameter, the sound name (this is different to the file name) which is the key given to the sound in the SoundsConfig sounds map.

playSound

Triggering this event will play the sound specified through once. If the sound name given does not exist in the sound map, nothing will happen. The sound will respect the sound volume set in the settings screen which could be set to 0. If this is the case, the sound will still play, although it will not be audible. Please ensure you have set the sound volume to be non-zero when testing your sounds.

Here is an example showing how to call this event. It assumes the component already exists on the entity.

entity.getEvents().trigger("playSound", "sound_1");

loopSound

Triggering this event will loop the sound indefinitely until the component is disposed or the stopSound event is triggered with the same sound name. If the sound name given does not exist in the sound map, nothing will happen. The sound will respect the sound volume set in the settings screen which could be set to 0. If this is the case, the sound will still play, although it will not be audible. Please ensure you have set the sound volume to be non-zero when testing your sounds.

Here is an example showing how to call this event. It assumes the component already exists on the entity.

entity.getEvents().trigger("loopSound", "sound_1");

stopSound

Triggering this event will stop a looping sound from playing. It will not stop a sound triggered via the playSound event. If the sound name given does not exist in the sound map or the specified sound is not looping, nothing will happen.

Here is an example showing how to call this event. It assumes the component already exists on the entity.

entity.getEvents().trigger("stopSound", "sound_1");

SoundsConfig

The SoundsConfig class is used to specify which sounds an entity may use throughout its lifetime. It consists of only one property, soundsMap which is an ObjectMap from a sound name to its file location. The sound name specified in the config is what will be used to play, loop, or stop it through events. It is recommended that you load in each SoundsConfig through a JSON file to make it easy to change the sounds used at a later date. This can be done by creating a JSON file with the following. format.

{
  "sound": {
    "soundsMap": {
      "sound_1": "sounds/sound_1.wav",
      "sound_2": "sounds/sound_2.wav"
    }
  }
}

This can then be loaded in as follows (where {file_name} is replaced with the name of the file).

SoundsConfig config = FileLoader.readClass(SoundsConfig.class, "configs/{file_name}.json");

If you are already using a config for the entity however, the SoundsConfig can instead be added to the existing config as follows by adding it as a public parameter in the existing config class. For example, take the EnemyConfig.

public class EnemyConfig extends HealthEntityConfig {
  public int speed = 1;
  public EnemyBehaviour behaviour;
  public EnemyType type;
  public boolean isBoss = false;
  public int specialAttack;
  // add SoundsConfig parameter
  public SoundsConfig sounds;
}

You must then add the sounds parameter to the existing config file like so.

{
  "health": 20,
  "baseAttack": 10,
  "speed": 5,
  "behaviour": "PTE",
  "type": "Melee",
  "spritePath": "images/enemy/base_enemy.atlas",
  "sounds": {
    "soundsMap": {
      "enemyDeath": "sounds/enemyDeath.mp3",
      "enemySpawn": "sounds/enemySpawner.mp3"
    }
  }
}

It can then be read in using the same method.

SoundsConfig config = FileLoader.readClass(EnemyConfig.class, "configs/enemy.json");

Sequence Diagram

Here is a sequence diagram to better show the interaction between the SoundComponent, Entity, EventHandler, and other components.

sequenceDiagram
    participant Entity
    participant AbstractComponent
    participant EventHandler
    participant SoundComponent
    participant Sound

    par setup
    AbstractComponent ->>+ Entity: addComponent(abstractComponent)
    SoundComponent ->>+ Entity: addComponent(soundComponent)
    SoundComponent->>+ Entity: events = getEvents()
    SoundComponent ->>+ EventHandler: events.addListener("playSound", playSound)
    SoundComponent ->>+ EventHandler: events.addListener("playSound", loopSound)
    SoundComponent ->>+ EventHandler: events.addListener("playSound", stopSound)
    AbstractComponent ->>+ Entity: events = getEvents()
    end
    par playSound
    AbstractComponent ->>+ EventHandler: events.trigger("playSound", "sound_1")
    EventHandler ->>+ SoundComponent: playSound("sound_1")
    SoundComponent ->>+ Sound: play()
    end
    par loopSound
    AbstractComponent ->>+ EventHandler: events.trigger("loopSound", "sound_2")
    EventHandler ->>+ SoundComponent: loopSound("sound_2")
    SoundComponent ->>+ Sound: loop()
    end
    par stopSound
    AbstractComponent ->>+ EventHandler: events.trigger("stopSound", "sound_2")
    EventHandler ->>+ SoundComponent: stopSound("sound_2")
    SoundComponent ->>+ Sound: stop()
    end