Następny artykuł - Wykrywanie kolizji i obsługa jednostek
[2]
Opublikowane na Wrocławski Portal Informatyczny (http://informatyka.wroc.pl)
Strona główna > Gra 2D, część 5: Odtwarzamy dźwięk
Gra 2D, część 5: Odtwarzamy dźwięk
03.02.2010 - Łukasz Milewski
Trudność
Możemy łatwo zwiększyć atrakcyjność naszej gry. Wystarczy wpłynąć na wyobraźnię gracza, tak
aby sam zaczął widzieć to, czego oczekuje. Jednym z najprostszych sposobów oddziaływania na
wyobraźnię jest dźwięk i muzyka. Stosując odpowiedni podkład muzyczny łatwo jest wpływać na
nastrój gracza - sprawiać aby był szczęśliwy i odprężony, albo żeby się wystraszył (jeżeli gra w
horror). Oprawa dźwiękowa jest jednym z najważniejszych elementów gier. Dlatego w tym artykule
zobaczymy jak można dodać ją do naszej produkcji.
Poprzedni artykuł - Hall of fame
[1]
Plan działania
Zaczniemy od przywrócenia gry do stanu, w którym możemy biegać graczem po mapie. Następnie
zaprogramujemy klasę Sound, która będzie odpowiedzialna za odtwarzanie muzyki i efektów dźwiękowych.
Na koniec dodamy muzykę do rozgrywki oraz dźwięk w momencie, gdy gracz będzie skakał.
Skopiuj pliki 05_game.mp3
[3]
oraz jump.wav
[4]
do katalogu data. Skopuj tam również również plik sounds.txt
[5]
.
kod początkowy
[6]
Linkowanie
Aby uruchomić kod z tego artykułu, potrzebujemy biblioteki SDL_mixer. Musimy zainstalować tę bibliotekę
oraz lekko zmodyfikować plik SConstruct, aby uwzględniał ją przy linkowaniu. Linijkę:
1
env.
MergeFlags
(
"-lSDL -lGL -lGLU"
)
;
zamieniamy na:
1
env.
MergeFlags
(
"-lSDL -lGL -lGLU -lSDL_mixer"
)
;
Interfejs klasy Sound
Naszym celem jest zaprogramowanie klasy do dźwięku i muzyki. Ostatecznie chcemy mieć możliwość
odtworzenia efektu dźwiękowego znając tylko jego nazwę.
Potrzebujemy nowy nagłówek "SDL/SDL_mixer.h". Chcemy móc załadować dźwięki i ich konfigurację z dysku
oraz mieć możliwość odtworzenia muzyki czy odgłosu. Te funkcje spełniają metody, odpowiednio:
LoadSounds, PlayMusic, PlaySfx, LoadMusic i LoadSfx posłużą do wczytania obiektu danych muzyki i danych
odgłosu (tak! to będą różne typy danych). W mapach m_sfx i m_music zapamiętamy odwzorowanie nazwy w
obiekt dźwięku.
Pokaż/ukryj kod
1
2
3
4
5
6
7
#ifndef __SOUND_H__
#define __SOUND_H__
#include <string>
#include <map>
#include <boost/shared_ptr.hpp>
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
1 z 7
2012-12-21 17:03
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <SDL/SDL_mixer.h>
class
Sound
{
public
:
explicit
Sound
()
;
void
LoadSounds
()
;
void
PlayMusic
(
const
std
::
string
&
name
)
;
void
PlaySfx
(
const
std
::
string
&
name
)
;
private
:
void
LoadMusic
(
const
std
::
string
&
name,
const
std
::
string
&
filename
)
;
void
LoadSfx
(
const
std
::
string
&
name,
const
std
::
string
&
filename
)
;
private
:
std
::
map
<
std
::
string
, Mix_Chunk
*
>
m_sfx
;
std
::
map
<
std
::
string
, Mix_Music
*
>
m_music
;
}
;
typedef
boost
::
shared_ptr
<
Sound
>
SoundPtr
;
#endif /* __SOUND_H__ */
Start!
Inicjalizacja
Na początek potrzebna jest nam lekka modyfikacja pliku App.cpp. Linijkę:
1
SDL_Init
(
SDL_INIT_VIDEO
)
;
zamieniamy na:
1
SDL_Init
(
SDL_INIT_VIDEO
|
SDL_INIT_AUDIO
)
;
W ten sposób inicjalizujemy dźwięk w bibliotece SDL. Musimy jeszcze zaimplementować inicjalizację w
konstruktorze Sound::Sound (plik Sound.cpp)
1
2
3
4
5
6
7
8
9
10
11
12
13
Sound
::
Sound
()
{
int
audio_rate
=
44100
;
Uint16 audio_format
=
AUDIO_S16SYS
;
int
audio_channels
=
2
;
int
audio_buffers
=
4096
;
if
(
Mix_OpenAudio
(
audio_rate, audio_format,
audio_channels, audio_buffers
)
!
=
0
)
{
std
::
cout
<<
"Unable to initialize audio: "
<<
Mix_GetError
()
<<
"
\n
"
;
exit
(
1
)
;
}
}
Przekazujemy parametry do funkcji Mix_OpenAudio. Ważny jest pierwszy (audio_rate). Określa on
częstotliwość próbkowania. Im większy, tym lepsza jakość i większe zużycie CPU (tak - SDL_mixer to dźwięk
programowy, a nie sprzętowy). Jeżeli zamiast 44100 wpiszemy 22050 to prawdopodobnie będzie słychać
szum.
Wczytanie danych z dysku
Zajmijmy się wczytaniem opisu dźwięków z pliku. Nasz plik (data/sound.txt) wygląda tak:
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
2 z 7
2012-12-21 17:03
1
2
music game data
/
game.
mp3
sfx jump data
/
jump.
wav
Pierwsza kolumna to typ dźwięku (muzyka lub sfx.). Druga to nazwa dźwięku (po tej nazwie będziemy
odwoływali się do dźwięku w programie). Ostatnia kolumna to ścieżka do pliku z dźwiękiem, jaki należy
załadować.
Kod jest bardzo prosty. Wczytujemy kolejne linijki. W każdej z nich wczytujemy najpierw typ i nazwę, a
następnie nazwę pliku. Z nazwy pliku usuwamy spacje. Ostatnim krokiem jest sprawdzenie, jakiego typu jest
nasz dźwięk i wczytanie go przy pomocy metody LoadMusic lub LoadSfx.
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void
Sound
::
LoadSounds
()
{
std
::
ifstream
settings
(
"data/sounds.txt"
)
;
std
::
string
type
;
std
::
string
name
;
std
::
string
filename
;
while
(
settings
)
{
settings
>>
type
>>
name
;
std
::
getline
(
settings, filename
)
;
filename.
erase
(
std
::
remove
(
filename.
begin
()
, filename.
end
()
,
' '
)
, filename.
end
())
;
if
(
type
==
"music"
)
{
LoadMusic
(
name, filename
)
;
}
else
if
(
type
==
"sfx"
)
{
LoadSfx
(
name, filename
)
;
}
else
{
std
::
cout
<<
"Unknown sound type: '"
<<
type
<<
"'
\n
"
;
}
}
}
Metody LoadMusic i LoadSfx są bardzo podobne. Aby załadować plik z muzyką, wywołujemy metodę
Mix_LoadMUS. Aby załadować plik z SFXem, wykorzystujemy Mix_LoadWAV. Te metody zwracają obiekty
odpowiednich dźwięków, które następnie kojarzymy ze sobą w mapach m_sfx i m_music.
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void
Sound
::
LoadMusic
(
const
std
::
string
&
name,
const
std
::
string
&
filename
)
{
Mix_Music
*
m
=
Mix_LoadMUS
(
filename.
c_str
())
;
if
(
m
)
{
m_music.
insert
(
std
::
make_pair
(
name, m
))
;
}
else
{
std
::
cout
<<
"Can't load music file "
<<
filename
<<
"
\n
"
;
}
}
void
Sound
::
LoadSfx
(
const
std
::
string
&
name,
const
std
::
string
&
filename
)
{
Mix_Chunk
*
c
=
Mix_LoadWAV
(
filename.
c_str
())
;
if
(
c
)
{
m_sfx.
insert
(
std
::
make_pair
(
name, c
))
;
}
else
{
std
::
cout
<<
"Can't load sfx file "
<<
filename
<<
"
\n
"
;
}
}
Odtworzenie wczytanych dźwięków
Pozostaje tylko odtwarzanie dźwięku. Tutaj ponownie metody dla odgłosów i muzyki się nie różnią.
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
3 z 7
2012-12-21 17:03
Sprawdzamy, czy dźwięk o podanej nazwie był wczytany. Jeżeli nie, wypisujemy odpowiedni komunikat.
Jeżeli tak, odtwarzamy go, wykorzystując odpowiednią funkcję SDL_mixera (Mix_PlayMusic lub
Mix_PlayChannel).
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void
Sound
::
PlayMusic
(
const
std
::
string
&
name
)
{
if
(
m_music.
find
(
name
)
==
m_music.
end
())
{
std
::
cout
<<
"Unknown music '"
<<
name
<<
"'
\n
"
;
}
else
{
Mix_PlayMusic
(
m_music
[
name
]
,
-
1
)
;
// -1 oznacza zapętlenie
}
}
void
Sound
::
PlaySfx
(
const
std
::
string
&
name
)
{
if
(
m_sfx.
find
(
name
)
==
m_sfx.
end
())
{
std
::
cout
<<
"Unknown sfx '"
<<
name
<<
"'
\n
"
;
}
else
{
if
(
Mix_PlayChannel
(
-
1
, m_sfx
[
name
]
,
0
)
==
-
1
)
{
std
::
cout
<<
"Unable to play sfx: '"
<<
name
<<
"'
\n
"
;
}
}
}
Dodajemy dźwięki do gry
OK. Mamy już wszystko, co potrzebne. Teraz zmieniamy klasę App.cpp (plik App.cpp). Na początek
przywróćmy chodzącego po mapie gracza (podczas poprzedniej lekcji usunęliśmy go). W tym celu zmieniamy
metodę Run. Linijki:
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pętla główna
ScoreSubmit ss
(
222
)
;
size_t
last_ticks
=
SDL_GetTicks
()
;
while
(
!
ss.
IsDone
())
{
ss.
ProcessEvents
()
;
// time update
size_t
ticks
=
SDL_GetTicks
()
;
double
delta_time
=
(
ticks
-
last_ticks
)
/
1000.0
;
last_ticks
=
ticks
;
// update & render
if
(
delta_time
>
0
)
{
ss.
Update
(
delta_time
)
;
}
ss.
Draw
()
;
}
zamieniamy na:
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
// pętla główna
size_t
last_ticks
=
SDL_GetTicks
()
;
while
(
!
is_done
)
{
ProcessEvents
()
;
// time update
size_t
ticks
=
SDL_GetTicks
()
;
double
delta_time
=
(
ticks
-
last_ticks
)
/
1000.0
;
last_ticks
=
ticks
;
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
4 z 7
2012-12-21 17:03
10
11
12
13
14
15
16
17
// update & render
if
(
delta_time
>
0
)
{
Update
(
delta_time
)
;
}
Draw
()
;
}
Teraz zmieniamy klasę Engine (plik Engine.h).
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef ENGINE_H_
#define ENGINE_H_
#include <string>
#include <GL/gl.h>
#include "SpriteConfig.h"
#include "Renderer.h"
class
Engine
{
public
:
static
Engine
&
Get
()
{
static
Engine engine
;
return
engine
;
}
void
Load
()
{
m_sprite_config.
reset
(
new
SpriteConfig
::
SpriteConfig
())
;
m_renderer.
reset
(
new
Renderer
::
Renderer
())
;
}
SpriteConfigPtr SpriteConfig
()
{
return
m_sprite_config
;
}
RendererPtr Renderer
()
{
return
m_renderer
;
}
private
:
SpriteConfigPtr m_sprite_config
;
RendererPtr m_renderer
;
}
;
#endif /* ENGINE_H_ */
Dodajemy wskaźnik na klasę dźwięku tak samo, jak np. na klasę Renderer. Odpowiedni kod po zmianach
wygląda następująco:
Pokaż/ukryj kod
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef ENGINE_H_
#define ENGINE_H_
#include <string>
#include <GL/gl.h>
#include "SpriteConfig.h"
#include "Renderer.h"
#include "Sound.h"
class
Engine
{
public
:
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
5 z 7
2012-12-21 17:03
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static
Engine
&
Get
()
{
static
Engine engine
;
return
engine
;
}
void
Load
()
{
m_sprite_config.
reset
(
new
SpriteConfig
::
SpriteConfig
())
;
m_renderer.
reset
(
new
Renderer
::
Renderer
())
;
m_sound.
reset
(
new
Sound
::
Sound
())
;
}
SpriteConfigPtr SpriteConfig
()
{
return
m_sprite_config
;
}
RendererPtr Renderer
()
{
return
m_renderer
;
}
SoundPtr Sound
()
{
return
m_sound
;
}
private
:
SpriteConfigPtr m_sprite_config
;
RendererPtr m_renderer
;
SoundPtr m_sound
;
}
;
#endif /* ENGINE_H_ */
Wracamy do App.cpp i przed komentarzem "pętla główna" dodajemy:
1
2
Engine
::
Get
()
.
Sound
()
-
>
LoadSounds
()
;
Engine
::
Get
()
.
Sound
()
-
>
PlayMusic
(
"game"
)
;
Zmieńmy jeszcze metodę skoku gracza (Player::Jump w pliku Player.cpp) z:
1
2
3
4
5
6
7
8
9
void
Player
::
Jump
(
double
y_velocity
)
{
// wykonaj skok o ile jest taka możliwość
if
(
m_jump_allowed
)
{
m_jump_allowed
=
false
;
m_is_on_ground
=
false
;
m_vy
=
y_velocity
;
// początkowa prędkość
m_ay
=
DefaultYAcceleration
;
// przyspieszenie
}
}
na:
1
2
3
4
5
6
7
8
9
10
void
Player
::
Jump
(
double
y_velocity
)
{
// wykonaj skok o ile jest taka możliwość
if
(
m_jump_allowed
)
{
m_jump_allowed
=
false
;
m_is_on_ground
=
false
;
m_vy
=
y_velocity
;
// początkowa prędkość
m_ay
=
DefaultYAcceleration
;
// przyspieszenie
Engine
::
Get
()
.
Sound
()
-
>
PlaySfx
(
"jump"
)
;
}
}
Jak widać, odgrywanie dźwięków jest bardzo proste! Nauczyliśmy się wczytywać własne pliki z danymi oraz
odtwarzać dźwięk i muzykę. W wyniku naszej pracy powstał nowy kod źródłowy, który jest dostępny tutaj
[7]
.
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
6 z 7
2012-12-21 17:03
Masz pytanie, uwagę? Zauważyłeś błąd? Powiedz o tym na forum
[8]
.
Zadania
Przywróć widok klawiatury ekranowej z poprzedniego artykułu (ScoreSubmit). Dodaj dźwięk klawiszy
naciskanych wtedy, gdy są wpisywane litery.
Spróbujmy zrobić odgłos kroków. W tym celu potrzebujemy odtworzyć zapętlony dźwięk, gdy gracz
zaczyna się poruszać i wyciszyć go, gdy się zatrzymuje. Za zapętlenie odpowiada ostatni argument
funkcji Mix_PlayChannel. Znajdź w internecie odpowiedni dźwięk i napisz metodę
Sound::PlaySfxLooped.
Zmień metody PlaySfx, aby zwracały id kanału (to, co zwraca funkcja Mix_PlayChannel). Gdy gracz
zaczyna iść, wywołaj metodę Sound::PlaySfxLooped("move"). Zapamiętaj zwrócone id. Gdy gracz się
zatrzyma, wywołaj metodę Sound::StopSfx(zapamiętane_id); metodę Sound::StopSfx zaimplementuj,
wykorzystując Mix_HaltChannel i podając jako argument id kanału.
Adres źródła: http://informatyka.wroc.pl/node/476
Odnośniki:
[1] http://informatyka.wroc.pl/node/475
[2] http://informatyka.wroc.pl/node/477
[3] http://informatyka.wroc.pl/upload/mmi/platf/05_game.mp3
[4] http://informatyka.wroc.pl/upload/mmi/platf/05_jump.wav
[5] http://informatyka.wroc.pl/upload/mmi/platf/05_sounds.txt
[6] http://informatyka.wroc.pl/upload/mmi/platf/05_kod_poczatkowy.zip
[7] http://informatyka.wroc.pl/upload/mmi/platf/05_kod_wynikowy.zip
[8] http://informatyka.wroc.pl/forum/viewtopic.php?f=55&t=358
Gra 2D, część 5: Odtwarzamy dźwięk
http://informatyka.wroc.pl/print/476
7 z 7
2012-12-21 17:03