« Return to Thread: suggestions on c++ game design with openAL most welcome

RE: suggestions on c++ game design with openAL most welcome

by Pingwah Leronz :: Rate this Message:

Reply to Author | View in Thread

Some parts of this message have been removed. Learn more about Nabble's security policy.
Thanks Samuel,
As for the bad class design /lack of encapsulation, oh yes definitely - I'd just posted up dummy classes like this, going public for simplicity's sake.

I've also gone with the multimap as the program's main data store as I need to access objects by their position - the gameworld is a 3 dimensional grid, with objects having absolute positions.  Initially I used a 3d array - AbstractObject[x][y][z] , but given i'm working with a large number of potential objects, using a predefined array like this eats up a *lot* of memory.
A multimap using one of my Coords classes , which contains x,y,z as the key allows me to quickly access members, by position, in a dynamically sized manner, and given multimaps are sorted by key, and the .find(key) func is a fairly quick Binary search, this method seems to suit.  I'm not sure you can do this with vectors? that is, quickly access a member based on one of its atributes, without iterating through them all to check?

The boost shared pointer gives me some ideas, ta. I didn't know about it.
>Out of the methods you listed below, Method 2 is probably more in the right direction.

...do you mean Method 1?  If im reading your sample code correctly, it's more like a global container of some structure which links a pointer to the object, with its associated sound source, or list of sources? 

Thanks,
Pingwah









CC: openal@...
From: space.ship.traveller@...
To: pingwah_leronz@...
Subject: Re: [Openal] suggestions on c++ game design with openAL most welcome
Date: Tue, 2 Jun 2009 21:11:38 +1200

Hello,

I have a number of comments for you.

Firstly, looking at your code I would suggest you review your naming conventions.

"class classCar" is not a conventional name. If it suits you, that is fine, but it seems like you've got an incredibly verbose naming scheme which many people will find hard to look at.

A good scheme is to have UpperCase class names which are typically common or proper nouns. Secondly, variables don't need to include their type in the name. This is generally considered tedious and unmaintainable. For example, fltFuel might change to dblFuel. But now you have to change a lot of code. There are lots of information regarding different kinds of coding conventions available on the internet.

Also, you have made all your member variables public. This is also considered harmeful, for example if you change the implementation or storage of fltFuel, this now becomes a project wide change. The better option is to rely on encapsulation which generally means you have getters/setters for key values related to the object you are trying to represent. For example, rather than having setFuel(...) you might have a function addFuel(...) which carries out logic such as checking the maximum fuel that can be added, etc, which in my experience reduces the complexity of code elsewhere.

Looking at the solutions you are proposing - consider what best models what you are trying to represent.

For example, if you have multiple cars, each car has an engine, therefore each engine produces a sound. We can consider this the ideal world. In computer programming we need to use tricks to achieve adequate performance. So, it might be the case that you have up to 5 sources dedicated to engine sounds, and then choose the 5 engines closest to the "camera" for actually playing audio. If you have more than "x" sources close to the "camera" it may become impossible for the listener to identify individual sounds anyway, so we can limit to 5 without any loss of apparent quality.

I would question your use of a multimap for storing cars. It seems incredibly complex and performance heavy.

Consider the use of a simple std::vector. This is very fast and has guaranteed O(1) performance for all operations if you are not concerned about order. To remove an element from a vector simply use std::swap and pop:

/// Provides an efficient way to erase elements from a std::vector
template <typename t>
bool eraseElementAtIndex (IndexT index, std::vector<t> & array)
{
if (array.size() == (index+1)) {
array.pop_back();
return false;
} else {
array[index] = array.back();
array.pop_back();
return true;
}
}

So we can achieve very quick storage with std::vector.

If you need to do space querying, for a small number of cars (< 20) brute force algorithm will be fine, such as simply iterating through the list  and choosing the closest cars, etc.

Finally in terms of structuring the OpenAL code the best option is to look at keeping audio completely separate from your "model".

Try to avoid things like:
     int MAX_SOUNDS = 150;

This generally considered bad practice (there are specific cases where it is okay, but generally use std::vector for this kind of allocation).

We should consider the importance of two kinds of sounds:
- Sound that caused by specific event that occurred (crash sound).
- Sound that has been started and stopped by a particular object (engine sound).

These two kinds of sounds can be managed differently. For example, often with sounds based of event, my experience is that we can't tell the difference between single source and multiple source in typical cases as long as the sound is short - for example, explosion sound. So in this case you can simply have a single source which is allocated when the game starts. This is very convenient. Alternatively you can allocate a list of sources and use them in a round robin fashion if you need more realism or the sound is longer.

For a sound that is continuous we either have a single source (i.e. music) or multiple sources (i.e. multiple engines). This case is slightly more complex especially if we separate audio function from object model (class Car). In this case, the easiest solution is to have the audio source as part of the class Car. This is the simplest solution and means that each car has its own audio source. But this isn't efficient for a large number of cars, and increases coupling between audio and your simulation which if it is running as a server / client, obviously you don't want the server making all sorts of sounds or sound simply may not be available.

Therefore, I recommend you look at the following structure:

using boost::shared_ptr;

class Car {
public:
Coord getPosition ();
};

class CarsController {
typedef shared_ptr<Car> CarPtr;
std::vector<CarPtr> m_cars;

std::vector<CarPtr> findCarsClosestToPoint (Coord c, unsigned maxCars);
void update (float dt);
};

class AudioSource
{
   ALuint intSource
 
   //funcs
   bool AssignBufferToThis(ALbyte *filename);
   void UpdatePosition(float x, float y, float z);
   void Play();
   void Stop();
   void PitchMod(float fltMod);
   void GainMod(float fltMod);
   void SetLooping(bool boolLooping);
   
   etc.
 
}

class CarAudioController {
// OpenAL mixer, etc

struct CarSource {
CarPtr car;
AudioSource source;
}
std::vector<CarSource> m_engineSources;

void playEngineSound (CarsController * carsController) {
// Step 1: Update source positions / stop sources that are no longer relevant, build a std::set of cars currently active
// Step 2: Query for cars that are close to the listener
// Step 3: For all these cars, check if they are currently in the set of active cars. If not, add it and start the appropriate engine sound.
// Step 4: Profit.
}
};

In terms of implementation, playEngineSound could implement a round robin scheme that is updated at set time intervals - depending on RPM, distance, etc you can update or stop the sound. There are many many options and this pseudo code is not well developed, but it should give you an idea of how to separate out the code. There are many ways to do this kind of separation - another way is using a delegate.

I also wrote an article you might find interesting/useful:

http://wiki.oriontransfer.org/blog:2009:05:06:what_is_abstraction

I hope something here helps. The question you asked has a lot of possible solutions. Out of the methods you listed below, Method 2 is probably more in the right direction.

Kind regards,
Samuel

On 2/06/2009, at 1:59 PM, Pingwah Leronz wrote:

First of all, hello everyone :)
 
 
I'm working on a c++ project that's moving into the early stages of audio design and facing what Im guessing would be a fairly common problem, and wondering if anyone has any advice or input about the best way to approach it.
 
For those interested - 
Let's say we have a car
class classCar()
{
    public:
    classCoords coordsPosition;
    classCoords coordsVelocity;
    float fltFuel;
    ...etc.
 
};
 
where classCoords is simply
coords
{
  public:
  float x, y, z;
 
};
 
and Cars are to be able to emit several sounds at once , an ambient engine roar, horn beeps, drivers yelling etc.
Also there's to be literally millions of cars (OK, in the project itself , they're not cars :) ), so a method of culling sound emitters from the set needs to be there.  
Criteria for adding and removing Cars from the audio set is simply proximity to a viewing position, and the cars themselves are stored in something like
 
 
multimap <classCoord*, classCar*> mapCars;
 
(again, they're not cars- let's say the track consists of many discrete positions, each of which can contain many cars).
 
I've been experimenting with two methods of handling this - the first with a global controller:
 
Method 1
---------------------------------
class classAudioController()
{
     int MAX_SOUNDS = 150;
     multimap <classCar*, ALuint> mapSounds;     //where ALuint here is a ref to the standard openal source[MAX_SOURCES]
 
     //functions
     void AddCar(classCar* newCar, ALuint* newAudioSource);
     bool AssignBufferToSource(ALuint *intSource, ALbyte* filename);
     void UpdateAllPositionsAndVelocities();
     void UpdateCarsPositionAndVelocity(classCar* findCar);
     etc.
 
 
};
 
 
and also by a more object oriented
 
Method 2
----------------------------------
classCar
{
   //position, fuel, health etc.
   //....
   classAudioSource *soundSource[MAX_SOURCES];   //remember, each car can emit many simultaneous sounds
 
 
}
 
where classAudioSource is 
{
   ALuint intSource
 
   //funcs
   bool AssignBufferToThis(ALbyte *filename);
   void UpdatePosition(float x, float y, float z);
   void Play();
   void Stop();
   void PitchMod(float fltMod);
   void GainMod(float fltMod);
   void SetLooping(bool boolLooping);
   
   etc.
 
}
 
 
 
With Method 1, Cars are added and removed from the emitters map on movement events, In method 2 classAudioSources are triggered to Play/Stop based on listener proximity , again on movement events.
I'm running into problems with both, mainly from unfamiliarity.
 
If anyone here has input into handling large sets of sound sources in a good OO-heavy way, or can point me to any reading that does I'd be massively grateful.
 
Thanks,
Pingwah
 
 
 
 
 


Find car news, reviews and more Looking to change your car this year? _______________________________________________
Openal mailing list
Openal@...
http://opensource.creative.com/mailman/listinfo/openal



Make ninemsn your homepage! Get the latest news, goss and sport
_______________________________________________
Openal mailing list
Openal@...
http://opensource.creative.com/mailman/listinfo/openal

 « Return to Thread: suggestions on c++ game design with openAL most welcome