Before deciding how to manage the banks in your game, it is strongly recommended that you read the following document carefully.
In an attempt to be as flexible as possible and to meet the requirements of almost any type of game, Wwise introduces a new approach for managing the banks in your game. This new approach does not invalidate the usefulness of the original method, but simply gives you more control and flexibility so that you can better manage the requirements of your games.
The new approach offers three major improvements over the existing traditional method:
The main advantage of this new approach is that it allows media content to be split into multiple memory banks. For example, let's say the music for the entire game is started using one single event. Using the traditional method, you would have added the event to the bank, which automatically added all the corresponding in-memory sounds and pre-fetched media, including the pre-fetch for the song that only plays at the end of the game. Storing all the media in memory for the entirety of the game seems very inefficient. Using the new approach, however, you can better manage your memory by splitting the music media into multiple banks so that it would be loaded only when sounds are likely to be played.
By splitting the media into multiple banks, you can also prioritize the media that is to be loaded. For example, in an environment with limited memory, you will want to load only the most important media. Non-critical media could be stored in a separate bank that would be loaded into memory only when there was enough room. Previously, both critical and non-critical media files were contained in the same bank. If the bank was too large to load into memory, none of the sounds would play, including the critical ones.
The following sections describe the different methods that can be used to generate and integrate the banks in your game. In a single game, you can use one or a combination of the different methods. Since every game is different, the method or methods you choose will depend on the specific requirements of your game.
The choices you make when creating your banks will have a significant impact on the amount of work that will be required to manage the audio assets in the game and will have a direct impact on the performance of your game. It is highly recommended that both the sound designer and audio programmer review this document carefully so that both are aware of the possibilities that are available. Working together, they should come up with a strategy that meets the specific needs of your game. Remember that all solutions will work, but the strategy you choose should take into consideration the memory usage, the I/O access, and the ease of integration in game. Each method has its advantages and drawbacks, so in most situations, it will be a question of balance between memory usage and ease of integration.
When using this method, all event content, sound structure data, and media files are stored in one bank that is loaded into memory at the same time.
This method will apply if:
Of course, most games generally don't have memory to waste, but this technique has the major advantage of being very simple to use and to maintain. The main reason for using this technique is to have the entire Wwise project integrated in the game in a minimum amount of time.
How's it done:
In Wwise
|
Note: The check boxes under the Events, Structures, and Media columns are all selected by default. This is good because, for this method, we want everything to be included in the bank. |
|
Tip: The advantage of adding complete work units to a bank instead of individual project elements is that you'll never have to re-edit the bank to reflect changes made to the contents of the work units. This is because Wwise maintains an active link between elements in a SoundBank and those in your project. If changes are made to the work units in your project, your SoundBanks are updated automatically. If you use this approach, a new set of SoundBanks can be generated by simply clicking the Generate button. |
|
Note: In a new project, only the default work unit is available. If more work units are created, these work units should also be added to the banks as required. |
In Game
Since there is only one SoundBank for this game, you can simply load it when initializing the game. Of course, the sound engine must be correctly initialized first.
... // Initialize the sound engine here. ... // Load the Init bank and the "All in one" bank. AkBankID bankID; // not used in this sample. AKRESULT eResult = AK::SoundEngine::LoadBank( L"Init.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"MyAllInOneBank.bnk", AK_DEFAULT_POOL_ID, bankID ); } ...
Additional Notes on this Method
Pros:
Cons:
|
Tip: Even though this method is a good way to quickly and easily integrate the audio in your game, don't wait until the end of the project to switch to a method that makes better usage of your game's memory. |
This method will apply if:
This method works well for single player games, where all possible sounds are only driven by the current location of the player in the game. By splitting the audio content into multiple banks, you can manage memory more efficiently than the first method, yet still benefit from a relatively easy integration of audio in your game.
How's it done:
First, you must determine how to split up your banks. For example, you may decide to split up your banks as follows:
In Wwise
In Game
In the game, simply load the right bank at the right time. For example, the game could load the general bank at the beginning and then load the other banks based on the player's actual location in the game. Note that some games will need to have enough memory to load more than one "level" at a time, to allow for transitions between levels.
... // Initialize the sound engine here. ... // Load Init bank and Common bank. AkBankID bankID; // Not used in this sample. AKRESULT eResult = AK::SoundEngine::LoadBank( L"Init.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"CommonEvents.bnk", AK_DEFAULT_POOL_ID, bankID ); } ... // And at various places in the code, based on the actual needs: eResult = AK::SoundEngine::LoadBank( L"Level_1.bnk", AK_DEFAULT_POOL_ID, bankID ); ... eResult = AK::SoundEngine::LoadBank( L"Level_2.bnk", AK_DEFAULT_POOL_ID, bankID ); ... eResult = AK::SoundEngine::LoadBank( L"Level_3.bnk", AK_DEFAULT_POOL_ID, bankID ); ... eResult = AK::SoundEngine::UnloadBank( L"Level_1.bnk", NULL ); ... eResult = AK::SoundEngine::UnloadBank( L"Level_2.bnk", NULL ); ... eResult = AK::SoundEngine::UnloadBank( L"Level_3.bnk", NULL );
Additional Notes on this Method
Pros:
Cons:
This method will apply if:
How's it done:
Games can be very complex and the triggering of sounds can be based on a variety of different factors, including game textures, the time of day, the movement of game objects, and in some cases the actions of other players for online multiplayer games. In an event-based or object-based environment, sounds could, for example, be loaded into memory based on the proximity of some other game objects. Every game object could have a list of banks that must be loaded when they are within a given range or simply because they exist.
Also, switches and states can determine which sounds are played. When an event playing such sounds is added to a bank, all possible media is automatically added to the bank as well. For example, you may have a single event called "Play_Footstep". This event will play the appropriate sound based on the current texture of the ground, which is specified by changing the switch. Although this works well, it can be a waste of memory keeping the sounds "footstep_sand.wav" or "footstep_winter.wav" ready in memory when the gameplay is happening inside a building in London.
To avoid wasting memory in this case, you can add events and/or sound structures to a bank and then specify which corresponding sounds will be included in the bank. If, for example, we take the footstep sounds on different textures, we could create the following banks:
In Wwise
To re-create the previous example:
Let's say you have three different textures in your game (snow, sand, and concrete). In Wwise, you have a switch container, that plays one of three random containers, based on the switch "ground_texture". Each of the three random containers has four variations of the footstep sound on a given texture.
|
Note: It would work as well if instead of drag and dropping the random containers we would drag and drop each of the sounds individually. But the advantage of using the containers is that all the sounds in the container will automatically be added in the bank instead of having to make all the changes manually if the container's content changes. |
At this point, we have four banks, one that contains the event and the structure data related to the audio that is to be played, and three others that contain only media associated with a particular ground texture.
In Game
// Load Init and the EventBank AkBankID bankID; // Not used in this sample. AKRESULT eResult = AK::SoundEngine::LoadBank( L"Init.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"EventBank.bnk", AK_DEFAULT_POOL_ID, bankID ); } if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"Common_Footstep_bank.bnk", AK_DEFAULT_POOL_ID, bankID ); } ... // And at various places in the code, possibly based on the location: eResult = AK::SoundEngine::LoadBank( L"Winter_Footstep_bank.bnk", AK_DEFAULT_POOL_ID, bankID ); ... eResult = AK::SoundEngine::LoadBank( L"Desert_Footstep_bank.bnk", AK_DEFAULT_POOL_ID, bankID ); ... eResult = AK::SoundEngine::UnloadBank( L"Winter_Footstep_bank.bnk", NULL ); ... eResult = AK::SoundEngine::UnloadBank( L"Desert_Footstep_bank.bnk", NULL );
Additional Notes on this Method
This was only a very specific example of one of many possible things that can be done using this technique. Since it is possible to decide sound by sound and event by event what will be included in every bank, you have complete control over the contents of each bank. Although you could create a separate bank for each sound in your game, this would be very difficult to maintain as every new sound would require new code to load the bank at the right place in the game. The goal for each game is to try and find a good balance between granularity and ease of integration in the game.
|
Tip: If you are interested in a sound by sound loading scheme, look at using either the PrepareEvent API, the PinEventInStreamCache API or the SetMedia API, instead of LoadBank. |
Pros:
Cons:
This method will apply if:
What exactly is a prepared event? When calling the PrepareEvent function, the system analyses the event and makes sure that all the structure and media related to this event are loaded into memory. If they are not, the system will automatically stream from disk the missing information. An event is prepared until it is explicitly unprepared.
How's it done:
This method requires that you explicitly exclude media from the bank that contains the metadata (in other words, structures and the events). The structures can be part of a separate SoundBank if desired; however, the granularity for loading structures is per-bank, so it often makes sense to include them in the same bank as the events.
When building banks that will use the PrepareEvent mechanism, the required event and structures must be found in at least one bank, and the media must be accessible as loose files in the file system. If the media referenced by a bank is not included in any other bank, the CopyStreamedFiles tool will assume that it will be used with the PrepareEvent API and will copy the loose file to the output directory.
Prior to preparing an event, the event itself must have been loaded into memory from one SoundBank (using LoadBank()). This is required because the event contains information about the dependencies required to prepare the event.
In Wwise
|
Note: The structure data contained within a single bank can't be split at run time. Therefore, if you are using AK::SoundEngine::PrepareEvent, and the structure data from a separate bank is required, all the structures within that bank will be loaded at once. For this reason, you may want to split the structure content in your project into multiple banks, to minimize the amount of unnecessary information that is loaded into memory. |
In Game
// Initializing the sound engine. AkInitSettings initSettings; AkPlatformInitSettings platformInitSettings; AK::SoundEngine::GetDefaultInitSettings( initSettings ); AK::SoundEngine::GetDefaultPlatformInitSettings( platformInitSettings ); // Set the required settings ... // Set PrepareEvent related settings initSettings.bEnableGameSyncPreparation = false; // Not used in the current sample. // Allocate a memory pool into which prepared media will be loaded. // If this is not done, prepare operations will fail. { // On the Xbox360, this must be allocated using the AkPhysicalAlloc (instead of AkMalloc) flag if the data may contain XMA encoded Audio. initSettings.uPrepareEventMemoryPoolID = AK.MemoryMgr.CreatePool( NULL, 4*1024*1024, 1024, AkMalloc ); // (Optional) Give the memory pool a name. This can be very useful for profiling. AK::MemoryMgr::SetPoolName( initSettings.uPrepareEventMemoryPoolID, L"PrepareEventPool" ); } AKRESULT eResult = AK.SoundEngine.Init( initSettings, platformInitSettings ); if( eResult != AK_Success ) { // Handle error. } // Load Init bank and the event/structure bank. AkBankID bankID; // Not used in this sample. AKRESULT eResult = AK::SoundEngine::LoadBank( L"Init.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"Events.bnk", AK_DEFAULT_POOL_ID, bankID ); } ... // And then, at various points in the code: const char * pEventsNameArray[1] = { "My_Event_Name" }; // Preparing an event: eResult = AK::SoundEngine::PrepareEvent( Preparation_Load, pEventsNameArray, 1 ); // 1 is the array size // Unpreparing an event: eResult = AK::SoundEngine::PrepareEvent( Preparation_Unload, pEventsNameArray, 1 ); // 1 is the array size
Additional Notes on this Method
Keep in mind that calls to AK::SoundEngine::PrepareEvent must be considered as I/O functions calls. In the previous example, we used blocking functions. You can use other overloads of the function AK::SoundEngine::PrepareEvent to make them non blocking calls, and then revive the completion notification through a separate callback.
Pros:
Cons:
This method will apply if:
This method is basically the same as the previous method (Method 4: Preparing Events), but with more control over the media that gets loaded when events are prepared. With this method, only the media associated to both the events that are prepared and the game syncs that are currently active is loaded into memory.
Let's say you have a simple project with two events: "Play_Maincharacter_FootSteps" and "Play_Monster_Footsteps". Each event plays a different switch container that plays different random sounds based on the ground texture under the moving character. The switch group name is "GroundTexture" and has three possible states: "Snow", "Concrete", and "Sand".
The switch container hierarchies in Wwise will look like the following:
Switch_Container_Footstep_Main_Character
and
Switch_Container_Footstep_Monster
In this example, we have 18 sounds (6 groups of 3 sounds) that can potentially be loaded into memory.
You could use the method Method 4: Preparing Events , but you won't get a level of granularity smaller than 6 sounds loaded in memory per event. You could use the method Method 3: Micromanaging Media to get a better level of granularity, but you would have to create 6 different banks for this simple project (the number of banks increases quickly in real project). Then, when a monster appears, you would have to check to see what textures are possible and then load the appropriate banks.
With the current method, all you have to do is specify which events and game syncs are possible and then only the appropriate media will be loaded. As with PrepareEvent, all the media must be available as loose files in the file system.
How it's done:
In Wwise
In Game
|
Note: The structure data contained within a single bank can't be split at run time. Therefore, if you are using AK::SoundEngine::PrepareEvent, and the structure data from a separate bank is required, all the structures within that bank will be loaded at once. For this reason, you may want to split the structure content in your project into multiple banks, to minimize the amount of unnecessary information that is loaded into memory. |
// Initializing the sound engine. AkInitSettings initSettings; AkPlatformInitSettings platformInitSettings; AK::SoundEngine::GetDefaultInitSettings( initSettings ); AK::SoundEngine::GetDefaultPlatformInitSettings( platformInitSettings ); // Set the required settings. ... // Set PrepareEvent related settings. // The flag bEnableGameSyncPreparation is set to true to activate // the prepare gamesync mechanism. When set to false, the media // associated with all game syncs is loaded and there is no need // to call AK::SoundEngine:PrepareGameSyncs. // // When set to true, no media that is game sync dependent will be // loaded unless the game sync is activated by calling AK::SoundEngine:PrepareGameSyncs initSettings.bEnableGameSyncPreparation = true; // Allocate a memory pool into which prepared media will be loaded. // If this is not done, prepare operations will fail. { // On the Xbox360, this must be allocated using the AkPhysicalAlloc (instead of AkMalloc) flag if the data may contain XMA encoded Audio. initSettings.uPrepareEventMemoryPoolID = AK.MemoryMgr.CreatePool( NULL, 4*1024*1024, 1024, AkMalloc ); // (Optional) Give the memory pool a name. This can be very useful for profiling. AK::MemoryMgr::SetPoolName( initSettings.uPrepareEventMemoryPoolID, L"PrepareEventPool" ); } AKRESULT eResult = AK.SoundEngine.Init( initSettings, platformInitSettings ); if( eResult != AK_Success ) { // Handle error. } // Load Init and the event/structure bank. AkBankID bankID; // Not used in this sample. AKRESULT eResult = AK::SoundEngine::LoadBank( L"Init.bnk", AK_DEFAULT_POOL_ID, bankID ); if( eResult == AK_Success ) { eResult = AK::SoundEngine::LoadBank( L"Events.bnk", AK_DEFAULT_POOL_ID, bankID ); } // ... At this point, // the two events are loaded, but not prepared. No media is currently loaded. const char * pNameArray[1]; // Prepare the main character footstep event. pNameArray[0] = "Play_Maincharacter_FootSteps"; eResult = AK::SoundEngine::PrepareEvent( Preparation_Load, pNameArray, 1 ); // 1 is the array size // ... At this point, // one event has been prepared, but no media has been loaded yet. // Now since concrete is always available in the game. pNameArray[0] = "Concrete"; eResult = AK::SoundEngine::PrepareGameSyncs( Preparation_Load, in_eType, "GroundTexture", pNameArray, 1 ); // ... At this point, // the 3 sounds, Sound_Concrete_main_1, Sound_Concrete_main_2, and Sound_Concrete_main_3 are loaded. // Now, let's say that the main character enters a land where there is snow. pNameArray[0] = "Snow"; eResult = AK::SoundEngine::PrepareGameSyncs( Preparation_Load, in_eType, "GroundTexture", pNameArray, 1 ); // ... At this point, // 3 more sounds just got loaded : Sound_Snow_main_1, Sound_Snow_main_2 and Sound_Snow_main_3 // Then let's say that a Monster suddenly appears. pNameArray[0] = "Play_Monster_Footsteps"; eResult = AK::SoundEngine::PrepareEvent( Preparation_Load, pEventsNameArray, 1 ); // 1 is the array size // ... At this point, // 6 more sounds just got loaded ( Sound_Concrete_Monster_1.2.3 and Sound_Snow_Monster_1.2.3 ) // And now our player decides to run away from the monster, and the monster goes after him. // They run so far that they arrive at a place where there is no snow anymore. pNameArray[0] = "Snow"; eResult = AK::SoundEngine::PrepareGameSyncs( Preparation_Unload, in_eType, "GroundTexture", pNameArray, 1 ); // ... At this point, // The 6 sounds that were related to snow ( Sound_Snow_Monster_1.2.3 and Sound_Snow_main_1.2.3 ) are unloaded from memory. ...
|
Note: The order in which you call AK::SoundEngine::PrpareEvent and AK::SoundEngine::PrepareGameSync is not important. Each time the state changes, the media pool is updated by crossmatching the events and the game syncs. |
Additional Notes on this Method
Pros:
Cons:
Questions? Problems? Need more info? Contact us, and we can help!
Visit our Support pageRegister your project and we'll help you get started with no strings attached!
Get started with Wwise