4. díl - Zvuky, hudba, klávesnice a myš v XNA

C# .NET XNA game studio Robotris Zvuky, hudba, klávesnice a myš v XNA

V minulém dílu seriálu XNA tutoriálů jsme si ukázali vykreslování spritů a písma. Dnes se podíváme na hudbu, zvukové efekty a reakci na klávesy.

Načtení obsahu

Stejně jako minule sprity, i zvuky a hudbu musíme nejprve načíst. Zvukové efekty jsou typu SoundEffect, hudba je typu Song. Do třídy s hrou tedy přidáme 2 proměnné:

private SoundEffect zvukRada;
public Song hudba_zardax;

Přesuňme se do metody LoadContent a zvuky načtěme z obsahu:

zvukRada = Content.Load<SoundEffect>(@"Zvuky\zvuk_rada");
hudba_zardax = Content.Load<Song>(@"Zvuky\hudba_zardax");

Hudba

Přehrávání hudby nám zajišťuje statická třída MediaPlayer. Zajímat nás bude především metoda Play, která bere jako parametr objekt typu Song, tedy hudbu, kterou chceme přehrát. Play zavoláme na konci metody LoadContent(), prototože právě tehdy je hudba načtena.

MediaPlayer.Play(hudba_zardax);

Vyzkoušíme :)

Pro potřeby dalšího programování si vytvoříme veřejnou metodu PrepniHudbu(), která bude brát jako parametr Song. Pokud tento song ještě nehraje, spustí ho. Pokud již hraje, nechá ho hrát a neudělá nic. Touto kontrolou se vyhneme situacím, kdy např. přijdeme do lokace, kde má hrát hudba co již hraje a tato hudba by začala hrát odznovu, přestože může prostě pokračovat. Aktuálně přehrávané písničky najdeme ve vlastnosti Query.ActiveSong. Jak je vidět, přehrávač má i jakýsi playlist, ten my v seriálu ale nevyužijeme. Metoda by mohla vypadat takto:

public void PrepniHudbu(Song hudba)
{
        // Nehraje již ta samá hudba?
        if (MediaPlayer.Queue.ActiveSong != hudba)
                MediaPlayer.Play(hudba);
}

Původní přehrání hudby změníme na volání této metody. Ještě dodáme, aby se hudba opakovala nastavením vlastnosti IsRepeating. Konec metody LoadContent() tedy vypadá takto:

MediaPlayer.IsRepeating = true;
PrepniHudbu(hudba_zardax);

Pro zachování zdravého rozumu doporučuji obsah metody PrepniHudbu() pro účely vývoje zakomentovat, nebo ji budete po několika dnech programování a zkoušení hry nenávidět :)

Zvukové efekty

Dále zde máme zvukové efekty, ty jsou velmi jednoduché. Kdykoli je potřebujeme přehrát, zavoláme na nich pouze metodu Play(). Zvuk si zkusíme spustit opět na konci metody LoadContent:

zvukRada.Play();

Vyzkoušíme.

Klávesnice

Nějakým způsobem bude hráč hru samozřejmě ovládat. XNA nám poskytuje širokou podporu ovladačů, tedy klávesnice, myši, ale i dalších zařízení, jako joysticků nebo gamepadů. My se teď zaměříme zejména na klávesnici.

Pro práci s klávesnicí máme k dispozici třídu Keyboard. Zajímat nás na ní bude zejména statická metoda GetState(), která nám vrátí stav aktuálně stisknutých kláves. Na instanci tohoto stavu se můžeme poté ptát, zda obsahuje konkrétní klávesu. Instance stavu kláves je typu KeyboardState. Veřejnou proměnnou tohoto typu si přidáme do třídy s hrou:

public KeyboardState klavesy;

Již víme, že místo, kde se budeme na stav kláves ptát, je právě metoda Update(). Přesuňme se do ní a za zpracováním GamePadu pro XBox si uložme aktuální instanci stavu kláves:

klavesy = Keyboard.GetState();

Nyní máme v klavesy uložený současný stav klávesnice. Na instanci stavu máme 3 užitečné metody:

  • IsKeyDown() - Zeptá se, zda je stisknuta určitá klávesa.
  • IsKeyUp() - Zeptá se, zda je uvolněna určitá klávesa.
  • GetPressedKeys() - Vrátí pole všech stisknutých kláves.

Jednotlivé klávesy rozlišujeme pomocí výčtového typu Keys. Prvek Keys (tedy jednu konkrétní klávesu) berou jako parametr první 2 metody. 3. metoda vrací pole prvků Keys.

Zeptejme se, zda je stisknuta klávesa enter a pokud ano, přehrajme náš zvuk:

if (klavesy.IsKeyDown(Keys.Enter))
        zvukRada.Play();

Vyzkoušíme.

Vidíme, že celý postup má jednu nevýhodu - klávesa se neustále vyhodnocuje po celou dobu, co ji držíme. Zvuků tedy hrají desítky přes sebe a nám z toho třeští hlava. Někdy opravdu můžeme chtít, aby hra takto reagovala na určitou klávesu, např. pokud držíme plyn, auto stále akceleruje, jakmile ho pustíme, otáčky klesají. Někdy ale chceme, aby se klávesa vyhodnotila jen jednou a podruhé pouze tehdy, když je puštěna a poté znovu stisknuta. Ukažme si, jak na to. K naší proměnné klavesy si výše deklarujme ještě jednu, reprezentující minulý stav kláves:

public KeyboardState klavesy, klavesyMinule;

Uložení stavu kláves v Update() upravme taky, aby v proměnné klavesyMinule byl minulý stav klávesnice:

klavesyMinule = klavesy;
klavesy = Keyboard.GetState();

Nyní není nic jednoduššího, než se zeptat, zda bylo enter minule puštěné a nyní je stisknuté:

if (klavesy.IsKeyDown(Keys.Enter) && klavesyMinule.IsKeyUp(Keys.Enter))
    zvukRada.Play();

Opět vyzkoušíme.

Jelikož takovéto chování budeme chtít často a musíme do podmínky psát tu samou klávesu 2x, vytvoříme si k tomuto účelu metodu NovaKlavesa(). Ta bude v parametru brát prvek výčtového typu Keys a zjistí, zda je klávesa nyní držena a minule držena nebyla:

public bool NovaKlavesa(Keys klavesa)
{
        return klavesy.IsKeyDown(klavesa) && klavesyMinule.IsKeyUp(klavesa);
}

Metoda je veřejná, protože ji budeme používat časem i mimo třídu s hrou. Upravíme naši reakci na enter v Update():

if (NovaKlavesa(Keys.Enter))
    zvukRada.Play();

Jistě uznáte, že nyní je zápis mnohem přehlednější.

Myš

Ještě máme trochu času a i když myš v našem tetrisu používat nebudeme, ukážeme si, jak se s ní pracuje. Neprve použijeme standardní kurzor Windows. Ten je standardně na okně s hrou vypnutý, zapneme ho přepnutím vlastnosti IsMouseVisible přímo na hře v metodě Initialize():

IsMouseVisible = true;

Takový kurzor je docela ošklivý, proto ho zas vypneme :) Jak jsem se již zmínil, naše hra používat myš nebude, proto jsem pro vás nepřipravil žádný sprite s kurzorem myši. Pokud chcete, nějaký si nakreslete a přidejte, já zde místo kurzoru vykreslím písmeno X.

Podobně jako klávesnice měla třídu Keyboard, pro myš nám XNA nachystalo třídu Mouse. Funguje úplně stejně, opět tu máme stav MouseState. Novou proměnnou mys tohoto typu si přidáme do třídy s hrou:

public MouseState mys;

V Update() si stav uložíme, stejně jako jsme to udělali se stavem kláves:

mys = Mouse.GetState();

Nyní se přesuneme do metody Draw(), kde vykreslíme "kurzor" a dále vypíšeme aktuální souřadnice a stav tlačítka.

Začneme kurzorem, zde stačí použít vlastností X a Y na instanci stavu myši, které označují pozici kurzoru. Kurzor vykreslíme jako poslední, aby byl nad vším ostatním (samozřejmě ale před spriteBatch.End()):

spriteBatch.TextSeStinem(fontCourierNew, "X", new Vector2(mys.X, mys.Y), Color.White);

Nyní vykreslíme pozici myši a stav tlačítka:

string text = String.Format("{0},{1} {2}", mys.X, mys.Y, mys.LeftButton);
spriteBatch.TextSeStinem(fontCourierNew, text, new Vector2(0, 700), Color.White);

Kolize kurzoru a obdélníku

Nakonec si ještě ukážeme, jak udělat kolizi obdélníku a bodu. Již víme, že XNA má strukturu Vector2. Ono má podobných struktur více, my použijeme Rectangle (obdélník) a Point (bod). Rozdíl mezi bodem a vektorem je ten, že bod má celočíselné souřadnice a postrádá metody jako vektorový součet. Přesto je v XNA zvykem používat téměř vždy vektor, jelikož s nimi pracují vnitřní metody XNA, např. metody vykreslovací.

Vytvořme si obdélník, který nastavíme přibližně na pozici robota na pozadí. Po kliknutí na robota se zobrazí text: "Neklikej na me" :) Nyní samozřejmě pouze bastlíme, v praxi bychom pracovali s objektem robot, to si časem ukážeme na objektu kostka. Do třídy si přidejme tyto proměnné:

private Rectangle obdelnikRobota;

V Initialize() obdélník nastavíme přibližně na souřadnice robota pomocí levého horního bodu a délky stran:

obdelnikRobota = new Rectangle(775, 345, 115, 245);

K číslům jsem došel tak, že jsem si pozadí otevřel ve Windows Malování a oblast označil, v dolní liště mi MSPaint ukázal její souřadnice a rozměry. Nyní se přesuňme do Draw() a přidejme vykreslení hlášky:

if (obdelnikRobota.Contains(new Point(mys.X, mys.Y))
&& (mys.LeftButton == ButtonState.Pressed))
        spriteBatch.TextSeStinem(fontBlox, "Neklikej na me", new Vector2(600, 220), Color.Lime);

Stav tlačítka porovnáváme pomocí výčtového typu ButtonState, obsahující prvky Pressed a Released (stisknuto a uvolněno). Ideálně by tato kontrola měla být v metodě Update(), ale pro náš pokus to nevadí. Pokud bychom chtěli složitější chování, stejně jako u kláves bychom založili proměnnou minulaMys.

Robotris, ukázková hra v XNA Game Studio

Příště se podíváme, jak hru rozložit do několika herních komponent. Celý projekt s dnešním řešením si opět můžete stáhnout níže.


 

Stáhnout

Staženo 322x (5.75 MB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (8 hlasů) :
4.6254.6254.6254.6254.625


 


Miniatura
Předchozí článek
Kreslíme a píšeme v XNA
Miniatura
Všechny články v sekci
Od nuly k tetrisu v XNA game studio
Miniatura
Následující článek
Rozdělení XNA hry do komponent

 

 

Komentáře
Zobrazit starší komentáře (4)

Avatar
Merry
Člen
Avatar
Merry:

No nějak podrobněji by to nešlo? Moc jsem nepochopil když to bude mít jiný tvar, např to kolečko, jinak díky :-)

Odpovědět 24.11.2012 17:14
Jste dobří jen v tom, co vás baví.
Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Událostí to jde, když si jí tam uděláš. Událost je delegát, pokud ti to něco říká, nebudeš mít problém ji zavést. Jak v tutoriálech píši, XNA je framework, ne engine.

Neobdélníkové kolize se ve hrách moc nepoužívají, zaprvé jsou zbytečné, zadruhé problémové a zatřetí pomalé. Pokud bys je z nějakého důvodu přeci jen potřeboval, pixely se z textury vytáhnou takto:

Color[] pixelColors = new Color[texture.Width * texture.Height];
texture.GetData(pixelColors);

Můžeš se podívat na tuto funkci, dá se z toho dost vyčíst:

static bool PerPixelCollision(Sprite a, Sprite b)
    {
        // Get Color data of each Texture
        Color[] bitsA = new Color[a.Texture.Width * a.Texture.Height];
        a.Texture.GetData(bitsA);
        Color[] bitsB = new Color[b.Texture.Width * b.Texture.Height];
        b.Texture.GetData(bitsB);

        // Calculate the intersecting rectangle
        int x1 = Math.Max(a.Bounds.X, b.Bounds.X);
        int x2 = Math.Min(a.Bounds.X + a.Bounds.Width, b.Bounds.X + b.Bounds.Width);

        int y1 = Math.Max(a.Bounds.Y, b.Bounds.Y);
        int y2 = Math.Min(a.Bounds.Y + a.Bounds.Height, b.Bounds.Y + b.Bounds.Height);

         // For each single pixel in the intersecting rectangle
         for (int y = y1; y < y2; ++y)
         {
             for (int x = x1; x < x2; ++x)
             {
                 // Get the color from each texture
                 Color a = bitsA[(x - a.Bounds.X) + (y - a.Bounds.Y)*a.Texture.Width];
                 Color b = bitsB[(x - b.Bounds.X) + (y - b.Bounds.Y)*b.Texture.Width];

                 if (a.A != 0 && b.A != 0) // If both colors are not transparent (the alpha channel is not 0), then there is a collision
                 {
                     return true;
                 }
             }
         }
        // If no collision occurred by now, we're clear.
        return false;
    }

Zbytek na http://gamedev.stackexchange.com/…ction-in-xna.

Odpovědět 26.11.2012 13:25
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Merry
Člen
Avatar
Merry:

Ok díky moc :-) vyzkouším

Odpovědět 28.11.2012 20:59
Jste dobří jen v tom, co vás baví.
Avatar
vojtomala
Člen
Avatar
vojtomala:

Ahoj chtěl bych se zeptat, když si vytvořím novou třídu(Add->class) pak ji podědím z třídy game1 a vytvářím si tam "ostatní" metody např. Prepnihudbu nebo NovaKlavesa(funguje to luxusně) ale chtěl bych se zeptat jakou výkonostní daň platím za tento luxus? :). díky za odpovědi :)

 
Odpovědět 27.6.2015 19:21
Avatar
Lukáš Hypša:

Ta hudba je krutá, klidně bych jí polouchal celou dobu :D ale nechci si ji znechutit už teď :D

Odpovědět 26. března 19:53
I když se programování učím jenom z interetu, velmi mě baví a doufám, že se tím jednou budu i živit.
Avatar
Petr Stastny
Redaktor
Avatar
Petr Stastny:

Ahoj, potřebuji pomoct. Zkoušel jsem si tohle udělat v Monogame, ale nechce mi to načíst jakoukoli hudbu. Ať už mp3, nebo wma, jako Song, nebo Soundeffect, ten soubor tam je, ale píše to, že to nenalezlo žádný soubor. Neví někdo, co s tím?

protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            font_Zakladni = Content.Load<SpriteFont>(@"Fonty\ArialBig");
            font_Flat = Content.Load<SpriteFont>(@"Fonty\FlatForm");
            font_FlatBig = Content.Load<SpriteFont>(@"Fonty\FlatForm_Big");
            sound_GameTheme = Content.Load<Song>(@"Zvuky\GameTheme");
            sound_MainTheme = Content.Load<Song>(@"Zvuky\MainTheme");


            PlayMusic(sound_MainTheme);
            MediaPlayer.IsRepeating = true;
        }

http://prntscr.com/bdwq9m
http://prntscr.com/bdwqtq
http://prntscr.com/bdwr64

 
Odpovědět 8. června 17:37
Avatar
vodacek
Redaktor
Avatar
Odpovídá na Petr Stastny
vodacek:

toto bude spíše chyba, že ti chybí nějaká knihovna, ohledně tohoto zkus napsat na forum monogame - tam svedou poradit lépe

 
Odpovědět 9. června 7:12
Avatar
Petr Stastny
Redaktor
Avatar
Petr Stastny:

Ahoj, potřebuji pomoct (zase :D) V monogame mi nefungovaly zvuky a ten pipeline byl takový ... no zajímavý :D

Takže jsem si nainstaloval XNA game studio a všechno funguje jak má :-) Jenom nevím, jak udělat tohle:

Mám ve hře dvě komponenty (herní obrazovky). Z obrazovky 1 přepínám na obrazovku 2. Ale mám problém: Na obrazovce 1 i 2 jsou tlačítka na stejných souřadnicích (a chci je tam kvůli 'designu' nechat). Takže když na obrazovce 1 kliknu na tlačítko, přehodí se to na obrazovku číslo 2 a tam se klikne na další tlačítko.

Nevíte, jak to vyřešit? Napadlo mě si to prostě udělat tak, že při klikání na tlačítko obrazovky 1 si uložím čas a nedovolím klikání na obrazovce 2, dokud neuplyne třeba vteřina. Ale asi to nebude ten nejlepší způsob. Bylo by to pomalé a složité, nemám pravdu? :-)

 
Odpovědět 16. června 18:43
Avatar
Petr Stastny
Redaktor
Avatar
Petr Stastny:

EDIT: A s tím souvisí problém, že mám klasická tlačítka, která nic nedělají (tedy myšleno nepřevádí pohled na jinou stránky). A na těch, když držím tlačítko, slyším hnusné pištění (používám SoundEffect pro klikání na tlačítka). Jak se toho mám zbavit?

if (rect_Back.Contains(new Point(hra.mys.X, hra.mys.Y)) && hra.mys.LeftButton == ButtonState.Pressed)
            {
                //
                // ...
                //
                seff_Click.Play();
                hra.PrepniHerniObrazovku(hra.komp_menu);
            }
 
Odpovědět 16. června 18:49
Avatar
Petr Stastny
Redaktor
Avatar
Odpovídá na Petr Stastny
Petr Stastny:

Tak dobrý, stačilo v Hra.cs vytvořit bool ButtonAllowed, v Update podmínku, že když myš není zmáčknutá, hodím ButtonAllowed na true a když se zmáčkne tlačítko, hodit ButtonAllowed na false. Tlačítko jde samozřejmě zmáčknout, jenom když je ButtonAllowed true;

Že mě to nenapadlo hned :-)

 
Odpovědět 17. června 14:47
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 14. Zobrazit vše