background image

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:
LoadSoundsPlayMusicPlaySfxLoadMusic 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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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