Game modding support

Hi

We are working on a strategic type game which will be moddable - mostly by adding new content. We decided to go with Programmer sounds and loading modded sounds from disk as wav or ogg (we know there is also a way with empty project, but we prefer this way). Our internal sounds are in Audio table and included into Banks.

In modded content there will be a number of sounds for each building, and each building can be instantiated multiple times. So we can imagine a situation, where there will be around 50 different buildings, each of them containing 5 different sounds, and each building instantiated up to 10 times. Math gives 2500 sounds on a scene (of course not playing all at once - this is controlled by attenuation and parameters - however they are looping sounds).

Our main concern is CPU usage and memory consumption. We did some testing and found out, that CPU usage is mostly dependent on sounds in range of our listeners - so this shouldn’t be a problem. But since each sound was loaded into memory (no matter if from the same sound file), memory consumption was very high. So we decided to preload each distinct sound and then into Event Callback pass pointer to that sound, instead of a string and creating sound inside callback.

Here is our setup. Is there anything, that we should be concern of? Maybe some other solution to this problem (compressing wav files on the fly?) or something we can improve (for sure we can wrap Sound and subsoundindex into a sruct and pass it into callback, so StudioSystem.getSoundInfo won’t be called twice - but anything else?)

  1. At the beginning, for each distinct audio file create a Sound
public static Sound CreateSound(string soundName) {
  var soundMode = MODE.CREATECOMPRESSEDSAMPLE | MODE.NONBLOCKING;
  Sound sound;

  if (RuntimeManager.StudioSystem.getSoundInfo(soundName, out var soundInfo)
      == RESULT.OK) {
    //Sound created by audio designer and included in Audio table
    RuntimeManager.CoreSystem.createSound(soundInfo.name_or_data,
                                          soundMode | soundInfo.mode,
                                          ref soundInfo.exinfo,
                                          out sound);
  } else {
    //Sound created by modders as wav on disk
    RuntimeManager.CoreSystem.createSound($"{Application.dataPath}/{soundName}.wav",
                                          soundMode,
                                          out sound);
  }
  return sound;
}
  1. When instantiating object create EventInstace and add a callback to it
public EventInstance CreateEvent(GameObject emitter, string eventName, Sound sound) {
  var eventInstance = RuntimeManager.CreateInstance($"event:/{eventName}");

  var soundHandle = GCHandle.Alloc(sound, GCHandleType.Pinned);
  eventInstance.setUserData(GCHandle.ToIntPtr(soundHandle));

  eventInstance.setCallback(_eventCallback);
  RuntimeManager.AttachInstanceToGameObject(eventInstance, emitter.transform, (Rigidbody) null);
  return eventInstance;
}
private static RESULT EventCallback(EVENT_CALLBACK_TYPE type, EventInstance instance,
                                    IntPtr parameterPtr) {
  switch (type) {
    case EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
      instance.getUserData(out var stringPtr);
      var soundHandle = GCHandle.FromIntPtr(stringPtr);
      if (soundHandle.Target is Sound sound) {
        var parameter = (PROGRAMMER_SOUND_PROPERTIES) Marshal.PtrToStructure(
            parameterPtr, typeof(PROGRAMMER_SOUND_PROPERTIES));
        sound.getName(out var soundName, int.MaxValue);
        RuntimeManager.StudioSystem.getSoundInfo(soundName, out var soundInfo);
        parameter.sound = sound.handle;
        parameter.subsoundIndex = soundInfo.subsoundindex;
        Marshal.StructureToPtr(parameter, parameterPtr, false);
      }
      break;
  }
  return RESULT.OK;
}

Your code and setup seem fine. Could you elaborate on what you meant by “each sound was loaded into memory (no matter if from the same sound file)”? Are you referring to creating multiple event instances that use this same sound file?

It would also be good to check that you are releasing the lowlevel sound in the EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND callback.