This page has been translated automatically.
Unigine Basics
1. Introduction
2. Managing Virtual Worlds
3. Preparing 3D Models
4. Materials
5. Cameras and Lighting
7. Making Cutscenes and Recording Videos
8. Preparing Your Project for Release
9. Physics
10. Optimization Basics
11. PROJECT2: First-Person Shooter
12. PROJECT3: Third-Person Cross-Country Arcade Racing Game
13. PROJECT4: VR Application With Simple Interaction

Playing Sounds

In addition to the visuals, sound is an important component of real-time solutions. It is sound that creates the feeling of immersion in the virtual world. A rumbling echo that makes you think the action is taking place in a spacious building, the soft tapping of footsteps on a stone floor or a car speeding past — all this can be simulated. UNIGINE provides a multi-channel sound system with stereo sound support based on HRTF (sound perception modeling function), various 3D effects, obstruction, and multiple sound reverberation. You can play sound in MP3, WAV, or OGA format when objects are in contact to simulate their physical properties at the sound level. For moving objects, the Doppler effect is simulated.

UNIGINE has two types of sound sources:

  • Sound Source — used to create directional sound sources.
  • Ambient Source — used for playing background music and sounds that can be heard throughout the scene.

A directional sound source can be added to a scene either in the Editor or via code. To add it via code, all you need to do is create an instance of the SoundSource class and specify all the necessary settings. Sound playback can be turned on and off using the play() and stop() methods.

Source code (C++)
// create a new sound source using the given sound sample file
SoundSourcePtr sound = SoundSource::create("sound.mp3");

// disable sound muffling when being occluded
sound->setOcclusion(0);
// set the distance at which the sound gets clear
sound->setMinDistance(10.0f);
// set the distance at which the sound becomes out of audible range
sound->setMaxDistance(100.0f);
// set the gain that result in attenuation of 6 dB
sound->setGain(0.5f);
// loop the sound
sound->setLoop(1);
// start playing the sound sample 
sound->play();

The sound played depends on the relative position of the sound sources and the listener. The sound is linearly attenuated within the specified range (MinDistance and MaxDistance). If inner and outer sound cones are set, they will also contribute to the attenuation factor (ConeInnerAngle and ConeOuterAngle). In addition, various objects in the scene can also block the propagation of sound from SoundSource (you can even set different sound absorption coefficients for different surfaces). The number of such sound sources is unlimited, as only those within hearing range are played.


To play background music and sounds that should be heard everywhere, you need to create an instance of the AmbientSource class (such sources can only be created via API). When creating it, the necessary parameters are also specified.

Notice
For an ambient source to be played, a Player is always required. In case an ambient source needs to be played when neither a world, nor the Editor are loaded, a Player, as well as the sound source should be created in the SystemLogic::init() method; otherwise, no sound will be heard.
Source code (C++)
// create a Player so that an ambient sound source is played
PlayerSpectatorPtr player = PlayerSpectator::create();
player->setPosition(Vec3(0.0f, -3.401f, 1.5f));
player->setViewDirection(vec3(0.0f, 1.0f, -0.4f));
Game::setPlayer(player);

// create the ambient sound source
AmbientSourcePtr sound = AmbientSource::create("sound.mp3");

// set necessary sound settings
sound->setGain(0.5f);
sound->setPitch(1.0f);
sound->setLoop(1);

// play the sound
sound->play();

Sound is processed in a separate thread at 30 frames per second, so changes are not applied instantly. In some cases, such as after stopping playback before changing track, it is necessary to force update the audio stream to avoid application errors.

Practice#

To create an atmosphere in our application, let's add the StereoSystem component to play music tracks using AmbientSource.

The component controls the stereo system in the room (switches it on and off, changes tracks).

StereoSystem.h
#pragma once
#include <UnigineComponentSystem.h>
#include <UnigineSounds.h>
#include <UnigineVisualizer.h>
#include "Interactable.h"
class StereoSystem :
    public Interactable
{
	// declare constructor and destructor for our class and define the name of the property to be associated with the component.
	// The Customizable.prop file containing all parameters listed below will be generated in your project's data folder after running the app for the first time
	COMPONENT_DEFINE(StereoSystem, Interactable);
	// list of tracks to be played
	PROP_ARRAY(File, sound_tracks);
	// registering methods to be called at the corresponding stages of the world logic (methods are declared in the protected-section below)
	COMPONENT_INIT(init);
	// Action method override for the child-component
	void action(int num = 0);
protected:
	// declaration of methods to be called at the corresponding stages of the world logic
	void init();

private:
	// current track number
	int current_track = 0;
	// source of the ambient sound (AmbientSource)
	Unigine::AmbientSourcePtr track_player = nullptr;
};
StereoSystem.cpp
#include "StereoSystem.h"
// Регистрация компонента StereoSystem
REGISTER_COMPONENT(StereoSystem);
using namespace Unigine;
using namespace Math;

void StereoSystem::init()
{
	// set the tooltip text that will be displayed when the cursor hovers over the object
	tooltip = "Right-click switches the sound tracks in a cyclic order.";

	// initialize the first track if the list is not empty
	if (sound_tracks.size() > 0)
		track_player = AmbientSource::create(Unigine::FileSystem::guidToPath(FileSystem::getGUID(sound_tracks[current_track].getRaw())));
	track_player->stop();
}

// Action method override for the child-component
void StereoSystem::action(int num)
{
	// action indices other than zero are invalid for this component, so they are ignored
	if (num != 0)
		return;
	// if the playlist is empty, do nothing
	if (sound_tracks.size() < 1)
		return;

	// if necessary, disable playback of the previous track before switching to the next one
	if (!track_player->isStopped()) {
		track_player->stop();
	}

	// after stopping playback before changing tracks, we need to force refresh the sound stream
	Sound::renderWorld(1);

	// change the track to an element of the indexed audio list with index
	track_player->setSampleName(Unigine::FileSystem::guidToPath(FileSystem::getGUID(sound_tracks[current_track].getRaw())));

	// display information about the current track
	Visualizer::renderMessage2D(vec3(0.0f, (float)(WindowManager::getMainWindow()->getClientSize().y - 40) / WindowManager::getMainWindow()->getClientSize().y, 0.0f),
		vec3(1.0f, 0.1f, 0.0f),
		String::format(" > Playing track №%d: %s", current_track + 1, sound_tracks[current_track].get()),
		Math::vec4_red, 1, 18, 1);
	track_player->setTime(0.0f);
	track_player->play();

	// increase the index of the current track (if it exceeds the number of tracks, set it to 0)
	current_track++;
	if (current_track >= sound_tracks.size())
		current_track = 0;
}

Let's save our files and then build and run our application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign our component to nodes. Close the application after running it and switch to UnigineEditor.

Assign the StereoSystem component to the tv1 node in the scene (interior -> tv1) and add tracks from the archviz/sounds folder to the Sound Tracks list:

Let's also add a few lines to the Fan component to play the fan sound. The important point here is that the Fan component is assigned to the fan_rotator node controlled by Toggle, so if the component disables the fan_rotator node, the logic of the Fan component assigned to it will not be executed and the sound will not be turned off. We need to make that part of the Fan component code (namely the updateSound() method) work even if the node is disabled and be executed every frame.

So, that's how we declare the updateSound method (passing execution order and invoke_disabled flag as second and third arguments):

Source code (C++)
COMPONENT_UPDATE(updateSound, 0 /*execution order*/, 1 /*invoke_disabled*/)

As a result, we have the following code and everything should work out well:

Fan.h
#pragma once
#include <UnigineComponentSystem.h>
class Fan :
	public Unigine::ComponentBase
{
public:
	// declare constructor and destructor for our class and define the name of the property to be associated with the component.
	// The Fan.prop file containing all parameters listed below will be generated in your project's data folder after running the app for the first time
	COMPONENT_DEFINE(Fan, ComponentBase);

	// declaration and initialization of component parameters
	PROP_PARAM(Node, fan_node, nullptr);			// fan blades node to be rotated
	PROP_PARAM(Float, speed, 720.0f);			// rotation speed

	// registering methods to be called at the corresponding stages of the world logic (methods are declared in the protected-section below)
	COMPONENT_INIT(init);
	COMPONENT_UPDATE(update);
${#HL}$
	// ensure that this method is executed even if the component is disabled along with the node
	COMPONENT_UPDATE(updateSound, 0, 1 /*invoke_disabled flag*/); ${HL#}$

protected:
	// declaration of methods to be called at the corresponding stages of the world logic
	void init();
	void update();
${#HL}$
	void updateSound();

private:
	// sound source
	Unigine::SoundSourcePtr sound = nullptr; ${HL#}$
	// fan sound to be played 
	Unigine::String soundFile ="archviz/sounds/fan_sound.mp3"; ${HL#}$
};
Fan.cpp
#include "Fan.h"
#include <UnigineGame.h>
// registering the Fan component
REGISTER_COMPONENT(Fan);

// inject necessary namespaces
using namespace Unigine;
using namespace Math;

// component's update method called each frame
void Fan::update()
{
	// if the fan node is not assigned, do nothing
	if (!fan_node)
		return;
	// rotate the node with the specified speed
	fan_node->rotate(0, speed * Game::getIFps() , 0);
}
${#HL}$
// init method to be called at the initialization stage
void Fan::init()
{
	// create a new sound source using the given sound sample file
	sound = SoundSource::create(Unigine::FileSystem::guidToPath(FileSystem::getGUID(soundFile.get())));

	// disable sound muffling when being occluded
	sound->setOcclusion(0);
	// set the distance at which the sound gets clear
	sound->setMinDistance(1.0f);
	// set the distance at which the sound becomes out of audible range
	sound->setMaxDistance(4.0f);
	// set the gain
	sound->setGain(0.5f);
	// loop the sound
	sound->setLoop(1);
	// set the sound source in the fan position
	sound->setWorldTransform(fan_node->getWorldTransform());
}

// updateSound method to be called each frame
void Fan::updateSound()
{
	// if the component is disabled and the sound is being played, turn off the playback
	if (!isEnabled() && sound->isPlaying()) {
		sound->stop();
		return;
	}
	// if the component is enabled and sound is not playing, enable playback
	if (isEnabled() && !sound->isPlaying())
		sound->play();
} ${HL#}$

Let's save our files and then build and run our application by hitting Ctrl + F5 to make the Component System regenerate the Fan property. Close the application after running it, switch to SDK Browser and launch our application by clicking the Run button on our project's card.

As a result, we have created an interactive room while getting acquainted with the UNIGINE component system and the essential aspects of working with various scene components from the code. This room allows us to arrange and remove objects, interact with them, modify their appearance, and play sounds.

Last update: 2024-11-06
Build: ()