di  -  martedì 14 settembre 2010

Introduzione


In questo articolo tratteremo il modulo mixer della libreria pygame. Le basi che abbiamo dato per iniziare a guardare con interesse la libreria non ci permettono ancora di raggiungere un livello soddisfacente.

Infatti non basta saper gestire immagini e input per realizzare un piccolo videogioco casalingo, perché per poterlo chiamare davvero “VIDEOGIOCO”, manca una cosa fondamentale: l’audio.

Il codice utilizzato in questo capitolo è lo stesso script utilizzato per l’esempio degli input, con la differenza che c’è una canzone come sottofondo, la quale può essere attivata o disattivata tramite il tasto “M” e si può regolare l’audio con i tasti “+” e “-” del tastierino numerico della vostra tastiera. Inoltre il mouse emetterò un suono diverso quando si clicca con il tasto destro e sinistro ed infine verrà emesso un applauso quando si clicca su start per l’inizio del gioco.

La musica di sottofondo è stata presa dal seguente sito http://www.opsound.org/music/, il suo nome è Relocation. Il sito si occupa di canzoni opensource, ovvero liberamente utilizzabili, così come gli effetti, che sono stati presi da http://www.pacdv.com/sounds/.
Potete scegliere voi stessi quali effetti o canzoni utilizzare, basta che non infrangano il copyright, ecco perché ho scelto canzoni liberamente fruibili, ma se siete musicisti, compositori
o avete una piccola vena artistica, basta un po’ di ingegno e un programma per ritoccare l’audio (come per esempio Audacity).

Il codice
Il codice verrà riportato per intero e poi commentato solo nelle differenze, cioè la parte dell’audio.
Si deve premettere che il mixer esegue file Ogg o Wav (non compresso) e che può essere gestito tramite i canali che la scheda audio mette a disposizione. Si possono controllare
volume e riproduzione e non è vietata la creazione di più canali, se necessario. Il mixer mette a disposizione anche un “canale” per la musica; ovvero potete gestire la musica di sottofondo del vostro gioco senza fare riferimento ad eventi Sound, che richiederebbero un grande spreco di memoria. Con il modulo Music invece, la vostra musica sarà passata in streaming, così da non doverla caricare completamente; in più Music supporta anche i file Mp3.

imm_sfondo = "introduzione.jpg"
mouse = "puntatore.png"

import pygame
from pygame.locals import *
from sys import exit

pygame.mixer.pre_init(44100, 16, 2, 4096)
pygame.init()

screen = pygame.display.set_mode((640,480), DOUBLEBUF | HWSURFACE, 32)
pygame.display.set_caption("Input Vari")

sfondo = pygame.image.load(imm_sfondo).convert()
puntatore = pygame.image.load(mouse).convert_alpha()

speed = 0.5
orologio = pygame.time.Clock()

tasto_start = pygame.Rect((200,100),(200,200))

controller = None

if pygame.joystick.get_count() > 0:
    controller = pygame.joystick.Joystick(0)
    controller.init()

if controller is None:
    print ("Siamo spiacenti, ma hai bisogno di un joystick con almeno 10 tasti!")

x , y = 0,0
move_x, move_y = 0,0

applausi = pygame.mixer.Sound("applause-2.wav")
click = pygame.mixer.Sound("beep-3.wav")
click2 = pygame.mixer.Sound("beep-6.wav")
pygame.mixer.music.load("Relocation.mp3")

playmusica = True
volume_musica = 1.0

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()

        elif event.type == KEYDOWN:
            tasti_premuti = pygame.key.get_pressed()

            if tasti_premuti[K_ESCAPE]:
                exit()

            if tasti_premuti[K_LALT] and tasti_premuti[K_F4]:
                exit()

            if event.key == K_UP:
                move_y = -1

            if event.key == K_DOWN:
                move_y = 1

            if event.key == K_LEFT:
                move_x = -1

            if event.key == K_RIGHT:
                move_x = 1

            if event.key == K_m:
                if playmusica:
                    playmusica = False
                else:
                    playmusica = True

            if event.key == K_KP_PLUS:
                if volume_musica < (1.0) and pygame.mixer.music.get_busy()==True:
                    volume_musica += 0.1
                    pygame.mixer.music.set_volume(volume_musica)

            if event.key == K_KP_MINUS:
                if volume_musica > (0.0) and pygame.mixer.music.get_busy()==True:
                    volume_musica -= 0.1
                    pygame.mixer.music.set_volume(volume_musica)

        elif event.type == KEYUP:

            if event.key == K_UP:
                move_y = 0

            if event.key == K_DOWN:
                move_y = 0

            if event.key == K_LEFT:
                move_x = 0

            if event.key == K_RIGHT:
                move_x = 0

        pulsanti_mouse = pygame.mouse.get_pressed()

        if pulsanti_mouse[0]==1:
            coordinate_mouse_attuali = pygame.mouse.get_pos()

            canaleclick = click.play()
            canaleclick.set_volume( 1.0 - (coordinate_mouse_attuali[0]/640.) , coordinate_mouse_attuali[0]/640.)

            if imm_sfondo == "introduzione.jpg":
                if tasto_start.collidepoint(coordinate_mouse_attuali):
                    canaleapp = applausi.play()
                    imm_sfondo = "giocoavviato.jpg"
                    sfondo = pygame.image.load(imm_sfondo).convert()

        if pulsanti_mouse[2]==1:

            canaleclick = click2.play()

            if imm_sfondo == "giocoavviato.jpg":
                imm_sfondo = "introduzione.jpg"
                sfondo = pygame.image.load(imm_sfondo).convert()

        if controller != None and controller.get_button(7):
            if imm_sfondo == "introduzione.jpg":

                canaleapp = applausi.play()

                imm_sfondo = "giocoavviato.jpg"
                sfondo = pygame.image.load(imm_sfondo).convert()

    if playmusica == True and pygame.mixer.music.get_busy()==False:
        pygame.mixer.music.play()

    if pygame.mixer.music.get_busy()==True and playmusica == False:
        pygame.mixer.music.stop()

    pygame.mouse.set_visible(False)
    coordinate_mouse = pygame.mouse.get_pos()

    tempo_passato = orologio.tick(60)
    tempo_passato_sec = tempo_passato/1000.0

    distanza_spostamento = tempo_passato*speed

    x += move_x*distanza_spostamento
    y += move_y*distanza_spostamento

    tasto_start.move_ip(move_x*distanza_spostamento,move_y*distanza_spostamento)

    screen.fill((0,0,0))
    screen.blit(sfondo,(x,y))
    screen.blit(puntatore,coordinate_mouse)

    pygame.display.flip()

Analisi

pygame.mixer.pre_init(44100, 16, 2, 4096)

Come potete notare, il mixer deve essere configurato prima dell’inizializzazioone.
In questo caso passiamo i parametri per gestire un mixer a 44100 Hz, 16 bit stereo e 4096 kb di buffer.
Fatto questo, una volta inizializzato pygame, non ci resta che memorizzare gli effetti sonori da noi utilizzati.

applausi = pygame.mixer.Sound("applause-2.wav")
click = pygame.mixer.Sound("beep-3.wav")
click2 = pygame.mixer.Sound("beep-6.wav")
pygame.mixer.music.load("Relocation.mp3")
playmusica = True
volume_musica = 1.0

Da notare come i click e gli applausi sono stati caricati come Sound, mentre la musica viene caricata a parte con pygame.mixer.music.load. Questo perché l’utilizzo è molto diverso e lo vediamo subito nella sezione eventi, dove sono state aggiunte le seguenti righe:

if event.key == K_m:
   if playmusica:
     playmusica = False
   else:
     playmusica = True

 if event.key == K_KP_PLUS:
   if volume_musica < (1.0) and pygame.mixer.music.get_busy()==True:
     volume_musica += 0.1
     pygame.mixer.music.set_volume(volume_musica)

 if event.key == K_KP_MINUS:
   if volume_musica > (0.0) and pygame.mixer.music.get_busy()==True:
     volume_musica -= 0.1
     pygame.mixer.music.set_volume(volume_musica)

In questo modo, controlleremo l’esecuzione della musica tramite il tasto “M”, facendo variare semplicemente playmusica. Invece con il tasto “+” e “-” andiamo a controllare se il volume è già al suo massimo, ovvero 1.0, e se la musica viene eseguita, aumentiamo e diminuiamo il volume. Questo controllo del volume viene effettuato solo sulla musica e non riguarda alcun effetto Sound.


canaleclick = click.play()
 canaleclick.set_volume( 1.0 - (coordinate_mouse_attuali[0]/640.) ,coordinate_mouse_attuali[0]/640.)

Come potete vedere ora, quando si clicca con il tasto sinistro del mouse viene avviato il suono click nel canaleclick. Canaleclick è proprio un Canale (Channel), poiché viene ritornato dalla funzione play() ed ha varie proprietà che possono essere modificate. Tra queste, ho scelto di modificare il volume. In poche parole, a seconda di dove pigiamo sulla schermata del nostro gioco, sentiremo il click sul canale destro o sinistro. Per fare questo, aggiorno tramite le coordinate del mouse il volume del canale dove viene riprodotto click. Vi ricordo che il volume è un valore float che va da 0.0 a 1.0 ed è per questo che divido per 640, visto che è la larghezza massima della nostra finestra. Si può notare subito la differenza con il click del tasto destro, sia nel codice che avviando il programma, poiché nel click destro del mouse c’è solo la seguente riga:


canaleclick = click2.play()

Per quanto riguarda gli applausi, sono solo disponibili quando si preme su start e si passa al gioco avviato:


if imm_sfondo == "introduzione.jpg":
  if tasto_start.collidepoint(coordinate_mouse_attuali):
     canaleapp = applausi.play()
     imm_sfondo = "giocoavviato.jpg"
     sfondo = pygame.image.load(imm_sfondo).convert()

Il metodo play accetta anche più parametri. Quelli che vi possono servire sono i primi due, ovvero loop e maxtime. Se indicate un numero maggiore di zero, il suono verrà eseguito una volta e poi n volte il numero da voi passato. Se invece volete ripetere un suono all’infinito ma lo volete limitare nel tempo, dovete solo fare come segue (il tempo è indicato in millisecondi):

canale = laser.play(-1, 5000)

Naturalmente il suono applausi è stato aggiunto anche nelle righe che riguarda la pressione del tasto start del joystick.
Per concludere, dobbiamo gestire lo scorrere della musica nel loop principale del gioco:

if playmusica == True and pygame.mixer.music.get_busy()==False:
        pygame.mixer.music.play()
if pygame.mixer.music.get_busy()==True and playmusica == False:
        pygame.mixer.music.stop()

Così controlliamo se la musica deve essere eseguita o meno. Il metodo pygame.mixer.music.get_busy() serve per vedere se la musica è già in esecuzione; è solo un controllo in più. In questo caso non servirebbe, ma per ovviare ad errori di inizializzazione, meglio controllare che la musica sia realmente in esecuzione. Potete gestire l’esecuzione della musica anche senza la variabile Booleana playmusica.

Conclusioni
Con questo articolo abbiamo dato la voce al nostro gioco. Nel prossimo cercheremo di mettere un pò d’ordine tra le cose che abbiamo imparato per poi passare ad elementi più complessi.

12 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    Cesare Di Mauro
     scrive: 

    Credo ci sia un errore qui: “In questo caso passiamo i parametri per gestire un mixer a 44100 Hz, 16 bit stereo e 4096 Mb di buffer.”. 4GB di buffer mi sembrano troppi. :)

    Nel codice, invece, c’è scritto 4046 e non 4096.

  • # 2
    Mirco Tracolli
     scrive: 

    In effetti sono 4 Mb e quindi 4096 Kb… Errore corretto! :D

  • # 3
    TheKaneB
     scrive: 

    io avrei aggiunto qualcosa di più “gustoso”, oltre al solito play/pause/volume…

    Ad esempio in un gioco sono molto apprezzati gli effetti sonori, anche semplici, come la modifica della velocità di riproduzione, il pitch (variazione di tono, normalmente legato alla velocità, ma può anche essere reso indipendente tramite interpolazione), l’echo, il chorus, il flanger, ecc… sono effetti relativamente semplici ma molto utili per rappresentare ambienti, situazioni e per veicolare emozioni e stati d’animo… (ad esempio dopo una fucilata a bruciapelo, in un FPS, mi aspetto di sentire solo i bassi, un fischio acutissimo in sottofondo, un po’ di rumore bianco, e un generale effetto di rimbombo tramite un echo ben calibrato)…

    Dai su, mi aspetto un po’ più “di sostanza” nel prossimo articolo :-)

  • # 4
    Mirco Tracolli
     scrive: 

    Ciao TheKaneB, purtroppo non ho avuto molto tempo da dedicare al sonoro e quindi non mi sono preoccupato di vari effetti (che grazie ad Audacity applicherò nelle prossime puntate) o giochi che si possono avere controllando i canali. Cecherò di recuperare il più presto possibile.

  • # 5
    TheKaneB
     scrive: 

    bene bene! :-)

    per quanto sia sottovalutato, il sonoro IMHO è l’elemento principale dell’esperienza videoludica, perchè tocca direttamente le sfere emotive della persona, a prescindere dalla sua estrazione socio-culturale.

    La maggior parte dei giochi musicali che conosco hanno avuto un successo enorme, come i vari Elite Beat Agents, Guitar Hero, Rock Band, Patapon ma anche i classicissimi Dance Dance Revolution, Step Mania e i vari sequencer e trackers che spesso vengono usati come videogiochi anzichè come strumenti di produzione musicale :-)

  • # 6
    Pluto
     scrive: 

    Ciao,

    Vorrei segnalare che con Debian Squeeze (che usa le librerie SDL 1.2.14)
    per inizializzare l’audio ho dovuto usare la seguente riga di codice:

    pygame.mixer.pre_init(44100, -16, 2, 4096)
    

    (-16 al posto di 16), cioè si devono usare campionamenti con segno.

    altrimenti ottengo l’errore:

    pygame.error: Unknown hardware audio format
    

    oppure:

    pygame.error: mixer system not initialized
    

    Immagino siano un problema delle librerie.

  • # 7
    Mirco Tracolli
     scrive: 

    @ Pluto

    Nessun problema. Come ho già risposto nella quinta parte, alcuni sistemi operativi hanno bisogno di questo parametro (-16), in particolare del “meno”, per dare la direttiva di lavorare con signed samples (Windows per esempio non ne ha bisogno). In termini qualitativi non cambia nulla, forse nel mio caso (utilizzo ArchLinux), l’utilizzo di pulse insieme ad alsa ha oltrepassato il problema.

  • # 8
    Pluto
     scrive: 

    Scusa, dovo ancora leggere il quinto post.

  • # 9
    Mirco Tracolli
     scrive: 

    @ Pluto

    Non ti preoccupare. L’importante è che il problema è stato risolto e compreso :D

  • # 10
    GiuseppeP
     scrive: 

    Salve, ho un problema col pygame.mixer.Sound, ovvero dopo aver fatto

    sound = pygame.mixer.Sound(“music/sound.mp3″)

    se faccio “sound.Play()” non parte, ma se lo stesso file lo metto come Music allora parte.
    Come risolvo?

  • # 11
    Mirco Tracolli
     scrive: 

    @GiuseppeP

    Scusa per il ritardo, ma non avevo controllato la posta.

    Per prima cosa, la funzione membro per un oggetto sound che fa iniziare la riproduzione del file è sound.play(), con la ‘p’ minuscola. Si possono impostare poi i parametri di loop, maxtime e fade_in. Comunque sia, questo non è il problema! :D

    Il problema sta nel fatto che gli oggetti Sound possono essere solo file OGG o unconpressed WAV. Mentre per quanto riguarda la musica il formato mp3 è abbastanza compatibile (nel senso che con alcuni os potrebbero esserci delle eccezioni, ma per usi comuni non dovresti avere problemi).

    Quindi se vuoi utilizzare dei file mp3 come Sound e non come Music, devi convertire i tuoi file in WAV o in OGG. Certamente è una forte limitazione, ma non più di tanto.

    Se desideri avere più liberà nel formato dei file utilizzabili devi chiedere aiuto ad altre librerie, che si possono perfettamente integrare insieme a pygame.

    Spero di non averti confuso troppo le idee e di non aver creato altri problemi :D

  • # 12
    GiuseppeP
     scrive: 

    Perfetto, ora funziona grazie mille :)

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.