Legend of Bob: Playing Sound with FMOD
A few month ago I gave up on the idea on using OpenAL for playing sound effect since trying to get MP3s to play with it was getting annoying. A friend recommended whilst in the Student Union to use FMOD, so I switched over and found it did everything I needed, plus there was plenty of documentation and examples.
So here is some basic code for getting audio playing using FMOD, as well as a few additional functions. I've only allocated blocks for playing 10 sounds (if more than 10 sounds are attempted to be loaded, LoadSoundByFile() returns -1).
Here is the header for my sound manager.
#ifndef SOUNDMANAGER_H_INCLUDED
#define SOUNDMANAGER_H_INCLUDED
// --- [ libraries ] ------------------------------------------
#pragma comment(lib, "fmodex_vc.lib")
// --- [ includes ] -------------------------------------------
#include <fmod.hpp>
// --- [ definitions ] ----------------------------------------
#define SOUND_LOOP_INFINITE -1
#define MAX_NUM_SOUNDS 10
// --- [ class ] ----------------------------------------------
class SoundManager
{
// Attributes
private
FMOD::System * m_pSystem;
FMOD::Sound * m_pSound[MAX_NUM_SOUNDS];
// Functions
public:
// Constructor
SoundManager();
// Destructor
~SoundManager();
// Load Sound from File
int LoadSoundFromFile( const char * inFilename );
// Free Sound
void FreeSound( const int inSoundId );
// Play Sound
void PlaySound( const int inSoundId );
// Set Position
void SetSoundPosition( const int inSoundId, const int inPositionInMilliseconds );
// Set Loop Count
void SetSoundLoopCount( const int inSoundId, const int inCount );
// Set Loop Points
void SetSoundLoopPoints( const int inSoundId, const int inStartInMilliseconds, const int inEndInMilliseconds );
};
#endif
For background music, I've also included code so that the sound can be looped forever, and since not all sounds will be looping from the beginning, I've added a function to set where the loop repeats from and begins again. When a sound is not being used anymore, FreeSound() should be called for the id returned by LoadSoundFromFile().
// --- [ includes ] -------------------------------------------
#include "SoundManager.h"
// --- [ constructor / destructor ] ---------------------------
SoundManager::SoundManager()
{
for( unsigned int i = 0; i < MAX_NUM_EFFECTS; ++i )
{
m_pSound[i] = 0;
}
// Create FMOD SoundSystem
FMOD::System_Create( &m_pSystem );
// Check FMOD Version
unsigned int fmodVersion;
m_pSystem->getVersion( &fmodVersion );
assert( fmodVersion >= FMOD_VERSION )
// Initialize FMOD SoundSystem
m_pSystem->init(32, FMOD_INIT_NORMAL, 0);
}
SoundManager::~SoundManager()
{
// Free FMOD Sounds
for( unsigned int i = 0; i < MAX_NUM_SOUNDS; ++i )
{
if( m_pSound[i] )
{
m_pSound[i]->release();
}
}
// Free FMOD SoundSystem
if( m_pSystem )
{
m_pSystem->close();
m_pSystem->release();
m_pSystem = 0;
}
}
// --- [ functions ] ------------------------------------------
int SoundManager::LoadSoundFromFile( const char * inFilename )
{
for( int i = 0; i < MAX_NUM_SOUNDS; ++i )
{
if( !m_pSound[i] )
{
m_pSystem->createSound( inFilename, FMOD_SOFTWARE, 0, &m_pSound[i] );
return i;
}
}
return -1;
}
void SoundManager::FreeSound( const int inSoundId )
{
assert( inSoundId >= 0 && inSoundId < MAX_NUM_SOUNDS );
if( m_pSound[inSoundId] )
{
m_pSound[inSoundId]->release();
m_pSound[inSoundId] = 0;
}
}
void SoundManager::PlaySound( const int inSoundId )
{
assert( inSoundId >= 0 && inSoundId < MAX_NUM_SOUNDS );
if( m_pSound[inSoundId] )
{
FMOD::Channel * channel;
m_pSystem->playSound( FMOD_CHANNEL_FREE, m_pSound[inSoundId], false, &channel );
}
}
void SetSoundPosition( const int inSoundId, const int inPositionInMilliseconds )
{
assert( inSoundId >= 0 && inSoundId < MAX_NUM_SOUNDS );
if( m_pSound[inSoundId] )
{
m_pSound[inSoundId]->setPosition( inPositionInMilliseconds, FMOD_TIMEUNIT_MS );
}
}
void SoundManager::SetSoundLoopCount( const int inSoundId, const int inCount )
{
assert( inSoundId >= 0 && inSoundId < MAX_NUM_SOUNDS );
if( m_pSound[inSoundId] )
{
m_pSound[inSoundId]->setLoopCount( inCount );
}
}
void SoundManager::SetSoundLoopPoints( const int inSoundId, const int inStartInMilliseconds, const int inEndInMilliseconds )
{
assert( inSoundId >= 0 && inSoundId < MAX_NUM_SOUNDS );
if( m_pSound[inSoundId] )
{
m_pSound[inSoundId]->setLoopPoints( inStartInMilliseconds, FMOD_TIMEUNIT_MS, inEndInMilliseconds, FMOD_TIMEUNIT_MS );
}
}
All timing in my code is in milliseconds, but it can be changed to another supported FMOD time unit.
Legend of Bob: Trees, Fog and Smoke
Finally after a lot of effort, I got the tree model I had down to 40,000 faces, making it almost acceptable to put into the Totus Village Map in my AGT project. It still got that great, the game lags a lot around the camera is facing that direction, probably because it is being cel shaded.
I've also added a smoke particle effect, though I'm currently "borrowing" the smoke from Wind Waker.
At some point I'll be creating a campfire model to place below the smoke, and a small particle effect for fire. One thing I'd like to add to the map eventually would be some street lamps.
One thing last thing I'm happy about getting into the game is fog, though it wasn't needed it showed me how to do something I've been trying to figure out.
I was playing around with the values and different fog equations when I got distant objects to flatten to silhouettes like the landscape does in World of Warcraft.
Legend of Bob: GUI
After playing around a bit with Ogre3D, I decided making a 2D quad and rendering a GUI just wasn't happening when I tried doing it manually, so I've decided to use the built in GUI, despite its limitations.
Before setting up a ".overlay" file for the GUI, I decided to manually enter the information into Ogre3D, and despite the texture coordinates being messed up, everything was fine.
Using C.E.G.U.I (Crazy Eddie's Graphical User Interface), I setup an overlay file for the message box that would be displayed when talking to NPCs.
I brought in the edges slightly after a recommendation from one of my housemates, and then move the faceset image up slightly.
AGT: Legend of Bob
After getting my Totus Village model to a point where it can be used in a game, I've decided to use it in my AGT project "Legend of Bob". It is still a work in progress, at the moment I only have a fish and a player model.
Over time I'll be upgrading the models and effects to be better designed as more of the engine becomes active. At the moment I'm using a large texture (4096x4096 pixels) for the ground texture.
This ground texture is used to composite other smaller textures using a method known as "texture splatting", which gives soft transitions between different textures depending on the mixture of colours.
I've used this method for transitioning between grass, sand, and rock.
The water has been upgraded from its original appearance to be more cartoon like, originally it was a semi transparent water texture.
Now the water texture is an opaque Toon shaded graphic that I found in a Ocean based game on the internet. At some point I'll hopefully get around to making my own.
The town model is the most impressive part of the first town so far, being 52618 faces, 40000 of which are the tree (almost as bad as that 30000 face fish I had at one point).
But it is slowly being optimized, removing and merging polygons, and then afterwards, it will be split up and loaded as separate models, before being compiled into static geometry.
I've modified my shaders to give rings of brightness depending on the closeness to the player, this gives a more toon like effect, however I think the dark values are too strong at the moment, and need to be more subtle.
AGT: Using XBox360 Pad with XInput
For my AGT assignment I've decided to use a XBox 360 pad for input since I found out it can be used in Windows. To do this requires use of the XInput library that comes with DirectX.
So after setting up my input manager which automatically updates all input devices (there can be a maximum of four pad devices, defined by the variable XUSER_MAX_COUNT in the XInput header), I began work on the controller class to manage the state information about the pad.
The following header is my source for this class, when a controller is created, an id is passed to it that is used as the pad index for XInput.
#ifndef CONTROLLER_H_INCLUDED
#define CONTROLLER_H_INCLUDED
// --- [ libraries ] ------------------------------------------
#pragma comment(lib, "XInput.lib")
// --- [ includes ] -------------------------------------------
#include <windows.h>
#include <XInput.h>
// --- [ class ] ----------------------------------------------
class Controller
{
//
public:
enum Button
{
kButton_DPad_Up = 0,
kButton_DPad_Down,
kButton_DPad_Left,
kButton_DPad_Right,
kButton_Start,
kButton_Back,
kButton_Thumb_Left,
kButton_Thumb_Right,
kButton_Shoulder_Left,
kButton_Shoulder_Right,
kButton_X,
kButton_Y,
kButton_A,
kButton_B
}
enum AxisType
{
kAxis_Left = 0,
kAxis_Right,
kNumAxisTypes
};
enum MotorType
{
kMotor_Left = 0,
kMotor_Right,
kNumMotorTypes
};
enum TriggerType
{
kTrigger_Left = 0,
kTrigger_Right,
kNumTriggerTypes
};
struct AxisData
{
double x;
double y;
};
// Attributes
private:
const unsigned int m_id;
bool m_isConnected;
unsigned int m_currentButtons;
unsigned int m_previousButtons;
AxisData m_axis[kNumAxisTypes];
float m_triggers[kNumTriggerTypes];
double m_motorSpeed[kNumMotorTypes];
// Functions
public:
// Constructor
Controller( const unsigned int inControllerId );
// Check button has just been pressed
bool IsButtonDown( const Button inButton ) const
{
return ( ~m_previousButtons & m_currentButtons ) & ( 1 << inButton );
}
// Check button has just been released
bool IsButtonUp( const Button inButton ) const
{
return ( m_previousButtons & ~m_currentButtons ) & ( 1 << inButton );
}
// Check button is pressed
bool IsButtonPressed( const Button inButton ) const
{
return m_currentButtons & ( 1 << inButton );
}
// Get Thumb Axis
void GetAxis( const AxisType inType, AxisData & outData ) const
{
outData = m_axis[inType];
}
// Get Trigger Values
float GetTrigger( const TriggerType inType ) const
{
return m_triggers[inType];
}
// Get Motor Speed
double GetMotorSpeed( const MotorType inType ) const
{
return m_motorSpeed[inType];
}
// Set MotorSpeed
void SetMotorSpeed( const MotorType inType, const double inValue );
// Check Controller is Connected
bool IsConnected() const
{
return m_isConnected;
}
// Get Controller Id
unsigned int GetId() const
{
return m_id;
}
// Update
void Update();
private:
// Update an Axis
void UpdateAxis( const AxisType inType, const short inX, const short inY );
// Update a Trigger
void UpdateTrigger( const TriggerType inType, const unsigned char inValue );
};
All values that are variable are values between 0.0 and 1.0 in the case of triggers and motor speeds, and -1.0 and 1.0 in the case of axis components. You may have noticed that there is only one function that can change values explicitly, SetMotorSpeed(), the only other way to change values is direct input from the controller, so Update() must be called every frame.
You may notice that the division used to normalise the speed is 0x7FFF instead of 0xFFFF, this is because the number is signed, not unsigned like the other values we normalise.
Calling Update() more than once will break the IsButtonUp() and IsButtonDown() calls to always return false since they are based on the previous button states gotten by an update call.
The following is the source code for the controller class source file.
// --- [ includes ] -------------------------------------------
#include "Controller.h"
// --- [ constructor / destructor ] ---------------------------
Controller::Controller( const unsigned int inControllerId )
:
m_id( inControllerId ),
m_isConnected( false ),
m_currentButtons( 0 ),
m_previousButtons( 0 )
{
for( unsigned int i = 0; i < kNumAxisTypes; ++i )
{
m_axis[i].x = 0.0;
m_axis[i].y = 0.0;
}
for( unsigned int i = 0; i < kNumTriggerTypes; ++i )
{
m_triggers[i] = 0.0f;
}
for( unsigned int i = 0; i < kNumMotorTypes; ++i )
{
m_motorSpeed[i] = 0.0f;
}
}
// --- [ functions ] ------------------------------------------
void Controller::Update()
{
XINPUT_STATE state;
// Get new controller state and check if it is connected
m_isConnected = XInputGetState( m_id, &state ) == ERROR_SUCCESS;
if( !m_isConnected )
{
return;
}
// Set New Axis Positions and normalise
UpdateAxis( kAxis_Left, state.Gamepad.sThumbLX, state.Gamepad.sThumbLY );
UpdateAxis( kAxis_Right, state.Gamepad.sThumbRX, state.Gamepad.sThumbRY );
// Set New Trigger Values and normalise
UpdateTrigger( kTrigger_Left, state.Gamepad.bLeftTrigger );
UpdateTrigger( kTrigger_Right, state.Gamepad.bRightTrigger );
// Set previous button states to m_currentButtons, and current button
// state to new button state.
m_previousButtons = m_currentButtons;
m_currentButtons = static_cast<unsigned int>( state.Gamepad.wButtons );
}
void Controller::SetMotorSpeed( const MotorType inType, const double inValue )
{
XINPUT_VIBRATION vibration;
m_motorSpeed[inType] = inValue;
vibration.wLeftMotorSpeed = static_cast<WORD>( 0xffff * m_motorSpeed[kMotor_Left] );
vibration.wRightMotorSpeed = static_cast<WORD>( 0xffff * m_motorSpeed[kMotor_Right] );
XInputSetState( m_id, &vibration );
}
void Controller::UpdateAxis( const AxisType inType, const short inX, const short inY )
{
const short kAxisDeadzones[kNumAxisTypes] =
{
XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE,
XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE
};
m_axis[inType].x = 0.0;
m_axis[inType].y = 0.0;
if( inX < -kAxisDeadzones[inType] || inX > kAxisDeadzones[inType] )
{
m_axis[inType].x = static_cast<double>( state.Gamepad.sThumbLX ) / static_cast<double>( 0x7fff );
}
if( inY < -kAxisDeadzones[inType] || inY > kAxisDeadzones[inType] )
{
m_axis[inType].y = static_cast<double>( state.Gamepad.sThumbLY ) / static_cast<double>( 0x7fff );
}
}
void Controller::UpdateTrigger( const TriggerType inType, const unsigned char inValue )
{
m_triggers[inType] = 0.0f;
if( inValue > XINPUT_GAMEPAD_TRIGGER_THRESHOLD )
{
m_triggers[inType] = static_cast<float>( inValue ) / static_cast<float>( 0xff );
}
}
And that is all that is required for getting state information from a XBox 360 pad.
AGT: Bored
Bored... nothing to do, so I decided to spend the day working on my A.G.T engine. And now its finished, no collision detection or interaction scripts yet, but the framework is essentially all there, but a lot of the managers finished.
In keeping with the limited dependency, I invented my own special version of O.O.P (Object Orientated Programming), that I like to called S.O.O.P (Steve's Object Orientated Programming) - All objects derived from a based object and have an identifier.
I'm sure I had a good reason for this when thinking it up, but for the life of me I can't remember what that was, so for now I'll probably just remove it.




