TOPlist

Vyvíjíme pro WP v XNA: Vstup z klávesnice, práce s daty (14. díl)

V dnešním díle budeme pokračovat s popisem užitečných triků pro vývoj XNA her pro Windows Phone. Podíváme se na možnosti, jakým způsobem se dají ve hrách načítat a ukládat data.

Načtení vstupu z klávesnice

Ve hrách občas budeme chtít získat vstup od uživatele. Ten si bude moci například kliknout na tlačítko a poté si vyplnit svoje jméno. To se mu následně zapíše například do tabulky rekordů. Pro získání vstupu od uživatele není potřeba vykreslovat na displej žádná vlastní nastavovátka, může se využít pomocná systémová komponenta. Uživateli vyskočí okénko s klávesnicí, do něj si vyplní vstup a potvrdí ho tlačítkem OK. Poté bude navrácen zpátky do hry. Hra si tento zadaný text následně nějakým způsobem zpracuje.

- 

Můžeme si to ukázat na příkladě. Do deklarací ve třídě Game1 si přidáme položku, ve které si budeme udržovat uživatelské jméno. Na začátku bude mít vyplněnou nějakou výchozí hodnotu:

string userName = "Default";

Na zvolené místo do metody Update() si doplníme volání zobrazení klávesnice:

if (!Guide.IsVisible)
    Guide.BeginShowKeyboardInput(PlayerIndex.One, "Vložte jméno", "Maximální délka je xxx znaků", "",
        new AsyncCallback(OnEndShowKeyboardInput), null);

Využívá se k tomu třída Guide. Jako parametry se předávají popisky dialogu a text, který by měl být předvyplněný v textovém poli. Předposlední parametr udává odkaz na metodu, která bude zavolána po dokončení vstupu. Její implementaci si můžeme také doplnit do třídy Game1:

private void OnEndShowKeyboardInput(IAsyncResult result)
{
    string name = Guide.EndShowKeyboardInput(result);
    if (name == null)
        return;
    userName = name;
}

V této metodě si načteme vstup získaný z dialogu a ověříme si, jestli není prázdný (jestli uživatel opravdu kliknul na OK, nestiskl Storno). Pokud text tedy existuje, uložíme si ho do naší proměnné userName.

Pomocí třídy Guide si můžeme ve hrách zobrazit i například systémový Message box (okénko s textem).

Užitečné může být také zablokování uspávání telefonu. Pokud hru budeme ovládat například naklápěním telefonu a nebudeme se dotýkat displeje, po určité době by mohl displej zhasnout a telefon se uspat. Tomu se dá zabránit nastavením položky IsScreenSaverEnabled. Může se to provést už například na začátku hry v metodě Initialize():

Guide.IsScreenSaverEnabled = false;
 

Uložení nastavení hry

Pokud si uživatel ve hře vyplní svoje jméno, určitě bude chtít, aby mu zůstalo nastavené i do dalšího spouštění. My tedy budeme chtít v naší hře položku userName někam uložit, při dalším spouštění hry si ji potom budeme chtít znovu načíst zpátky.

Jak už jsme si říkali v předchozích dílech, Windows Phone telefony nemají přístup ke standardnímu souborovému systému. Každá aplikace běží ve svém odděleném paměťovém prostoru, do kterého si může ukládat svoje data. Různé aplikace se ale navzájem ke svým datům nedostanou, nemohou si je jedna druhé upravovat nebo mazat. Tento oddělený prostor se nazývá Isolated Storage. Všechna data, která se do něj uloží, zůstanou uložená v telefonu i poté, co se hra vypne.

Do Isolated Storage si naše hry mohou ukládat data dvěma způsoby. Pro jednoduché uložení proměnných, obsahu polí nebo jednotlivých objektů slouží kolekce IsolatedStorageSettings. To je kolekce typu klíč/hodnota, přistupuje se k ní stejně, jako k objektu typu Dictionary. Podle textového řetězce (klíče) se do ní může vložit jakákoliv hodnota. Podle tohoto řetězce se potom může zase načíst zpátky.

Pro použití této kolekce z XNA je si potřeba přidat do projektu referenci na System.Windows (přes nabídku Add reference… v paletce Solution Explorer). Následně na začátek třídy Game1 vložit potřebný using:

using System.IO.IsolatedStorage;

Potom je už možné tuto kolekci začít používat. Uložení položky userName se provede například takto:

IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
string key = "username";
if (settings.Contains(key))
    settings[key] = userName;
else settings.Add(key, userName);
settings.Save();

A následné načtení zpět například takto – opět si tento kód doplníme podle svého uvážení na potřebné místo, v tomto případě například do metody Initialize():

if (!settings.Contains(key))
    settings.Add(key, "Default");
userName = (string)settings[key];

Pokud si tento text ve hře nastavíme a například vykreslíme na displej, hru potom vypneme a znovu spustíme, měli bychom vidět opět stejný text. Tato data by měla zůstat uložená v telefonu i tehdy, pokud si aplikaci upravíme a znovu zkompilujeme. Jedině pokud aplikaci změníme položku GUID v okénku Assembly Information, bude už považována za novou aplikaci. V seznamu aplikací v telefonu bude reprezentovaná novou ikonkou, bude mít už přístup k jinému paměťovému prostoru a k původním datům se nedostane. Je dobré pamatovat, že případě emulátoru budou také všechna data z Isolated Storage vymazána, pokud emulátor úplně zavřeme.

Stejným způsobem jde do kolekce IsolatedStorageSettings uložit i celé pole hodnot, například deset nejvyšších dosažených skóre ve hře. Následující řádky nastiňují, jak by se takové uložení a načtení obsahu pole zapsalo v syntaxi jazyka C#. Pro korektní funkčnost by se opět mělo navíc ověřit, zda se již v kolekci takový klíč nenachází a měla by se zavolat metoda Save():

int[] values = new int[10]; // values[0]=... values[1]=... settings.Add("rekordy", values);

values = (int[])settings["rekordy"];

Pokud budeme s tímto úložištěm pracovat pokročilejším způsobem, bude dobré ještě pamatovat na to, že třída IsolatedStorageSettings není thread safe. Kolekce se může dostat do nedefinovaného stavu, pokud se do ní bude zároveď zapisovat a zároveň číst z nějakého jiného vlákna. Každý přístup k ní by se měl v tomto případě raději zamykat, například pomocí konstrukce lock.

Ukládání souborů do Isolated Storage

Do Isolated Storage je možné ukládat ze hry i přímo celé soubory. Používá se třída IsolatedStorageFile ze jmenného prostoru System.IO. Soubory se mohou zapisovat nebo znovu načítat pomocí standardních streamů. Pro ukázku, jak může vypadat uložení textury s obrázkem (v proměnné bitmap) do souboru typu .png:

IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
using (var isoFileStream = new IsolatedStorageFileStream("obr.png", FileMode.OpenOrCreate, storage))
using (var isoFileWriter = new StreamWriter(isoFileStream))
    bitmap.SaveAsPng(isoFileStream, bitmap.Width, bitmap.Height);

Do Isolated Storage dané aplikace se uloží obrázek s názvem obr.png. Tímto způsobem si budeme moci například odzkoušet, zda se v naší textuře nachází opravdu data, která předpokládáme. Podobným způsobem si můžeme například vytvořit textový soubor a uložit si do něj určitý text (deklarace proměnné storage zůstane stejná, jako v minulém případě):

using (var isoFileStream = new IsolatedStorageFileStream("soubor.txt", FileMode.OpenOrCreate, storage))
using (var isoFileWriter = new StreamWriter(isoFileStream))
{
    isoFileWriter.WriteLine("První řádka");
    isoFileWriter.Write("Další text souboru");
}

Důležité bude zmínit, jak se k těmto uloženým souborům následně budeme moci dostat. Bude se nám hodit například aplikace WP7 Storage Explorer dostupná na CodePlexu. Funguje buď jako samostatná aplikace, nebo jako plugin do Visual Studia. Abychom ji mohli začít využívat, do projektu si je potřeba přidat na ni referenci (v paletce Solution Explorer přes Add Reference…), v záložce Browse vybrat složku WP7 Isolated Storage Explorer/Library ve složce Program Files (x86) a soubor IsolatedStorageExplorer.dll.

Do metody Initialize() se potom může přidat toto volání:

IsolatedStorageExplorer.Explorer.Start("localhost");

Když si nyní spustíme naši hru, například v emulátoru, a zároveň si spustíme program WP7 Storage Explorer z nabídky Start, měli bychom nyní vidět všechny naše uložené soubory. Kliknutím pravým tlačítkem myši můžeme zvolit uložení souboru do počítače.

-

Při ukládání dat v telefonu nejsme omezeni žádným limitem, můžeme tam ukládat data až do zaplnění celé jeho paměti. Od aktualizace Mango je možné využít pro ukládání dat i lokální databázové úložiště, je k dispozici SQL CE. K této databázi se dá přistupovat i pomocí technologie Linq to SQL (tato technologie zde ale zatím nebude popisována).

Obdobným způsobem, jako jsme do Isolated Storage ukládali soubory, si je z něj můžeme načítat i zpátky do hry. Pouze místo objektu typu StreamWriter použijeme objekt typu StreamReader.

Všechny tyto způsoby ukládání dat do Isolated Storage ale budou omezeny na použití pouze z jedné aplikace. Pokud budeme chtít, aby spolu dvě aplikace nebo hry komunikovaly navzájem a sdílely si data, tyto soubory si budeme muset ukládat někam na internet (do cloudu). Jinak se nám to bohužel nepodaří, musíme to brát jako omezení platformy.

Načítání popisu hry podle XML

Isolated Storage je bráno jako úložiště, do kterého si může hra za svého běhu ukládat nastavení nebo soubory. Často budeme chtít načítat data do projektu, která už budeme mít předem připravená (například s popisem herních levelů). Pro načítání takovýchto souborů existuje v XNA několik přístupů. Většinou tyto soubory budeme mít ve formátu XML, budou obsahovat uložené pozice a parametry objektů v naší hře. Při kompilaci se budou kopírovat do výsledného XAP balíčku zároveň se hrou.

Do složky Content našeho projektu si pro ukázku vložíme nový XML soubor. Vygeneruje se nám jeho vzorová podoba:

<?xml version="1.0" encoding="utf-8" ?> <XnaContent>
<!--
TODO: nahraďte tento Asset vlastními informacemi o herních prvcích. -->
<
Asset Type="System.String">UlozenyText</Asset> </XnaContent>

Do tagu XnaContent si můžeme vložit jakákoliv vlastní data (za předpokladu, že si je budeme také sami zpracovávat). Tagy si můžeme pojmenovat podle sebe, stejně tak i jejich obsah si můžeme vyplnit po svém. XML formát udává jen strukturu dat, neurčuje, jaké konkrétní položky by se tam měly nacházet.

Pro načítání dat z XML existuje mnoho chytrých přístupů, jako například technologie LINQ to XML a přístup pomocí třídy XDocument. V určitých případech je možné podle XML objekty do hry i automaticky serializovat. V XNA lze pro toto načítání využít i přímo Content Pipeline, lze si pro ni napsat i rozšíření. Další díly tohoto seriálu, které se budou věnovat Silverlightu, se k načítání dat do programů určitě ještě vrátí.

Tady si ukážeme jen základní přístup načítání z XML, takzvané streamové čtení. Data z XML si budeme číst od začátku – tag po tagu. Podle názvů tagů a jejich hodnot si budeme postupně vytvářet objekty ve hře. Místo připraveného tagu Asset si do XML souboru vložíme například tyto položky:

<SpriteInfo X="20" Y="50" Speed="1.1" AssetName="obrazek" />
<
SpriteInfo X="100" Y="50" Speed="1.5" AssetName="obrazek" />

V paletce Properties si u XML souboru změníme položku Build Action na None a položku Copy To Output Directory na Copy if newer. Bude to znamenat, že nechceme pro jeho načítání využívat Content Pipeline, ale budeme si ho zpracovávat sami.

Soubor si budeme postupně procházet v metodě LoadContent(). Podle tohoto popisu si budeme postupně vytvářet obrázky a budeme si je rozmisťovat na zadané pozice. Každý takový objekt bude pro ukázku obsahovat i položku Speed (typu float), abychom si ukázali i způsob, jak se z XML načítají desetinná čísla. Celý tento popis bude představovat nějaký jednoduchý herní level, například pozice protivníků a jejich rychlosti střílení. Položka AssetName bude určovat, jaká textura bude do obrázku načtena.

Pro reprezentaci těchto objektů využijeme třídu Sprite, podobnou, jako jsme si už v tomto seriálu programovali. Bude obsahovat například dvě celočíselné položky X a Y, položku Speed typu float a odkaz na texturu:

public class Sprite {
    public int X, Y;
    public float Speed;
    public Texture2D Texture;
}

Do třídy Game1 si přidáme deklaraci pole, ve kterém si budeme udržovat tyto objekty:

List<Sprite> sprites = new List<Sprite>();

A do metody LoadContent() si doplníme volání načítací metody:

LoadDataFromXML("Content\\Data.xml");

Do této metody si nyní připravíme samotné parsování souboru. Soubor si projdeme postupně až do konce, za každý nalezený tag SpriteInfo si vytvoříme nový objekt typu Sprite a přidáme si ho do kolekce sprites:

private void LoadDataFromXML(string path)
{            
    sprites.Clear();
    using (XmlReader reader = XmlReader.Create(path))
        while (reader.Read())
            if (reader.NodeType == XmlNodeType.Element && reader.Name == "SpriteInfo")
            {
                Sprite s = new Sprite();
                s.X = Convert.ToInt32(reader.GetAttribute("X"));
                s.Y = Convert.ToInt32(reader.GetAttribute("Y"));
                s.Speed = Convert.ToSingle(reader.GetAttribute("Speed"), System.Globalization.CultureInfo.InvariantCulture);
                s.Texture = Content.Load<Texture2D>(reader.GetAttribute("AssetName"));
                sprites.Add(s);
            }
}

Potřebné hodnoty objektu získáme z atributů daného tagu. V případě získávání parametru Speed (typu float) si ještě můžete všimnout druhého parametru metody Convert.ToSingle(). Zadává se tam jazyková kultura. Je to proto, že v některých jazycích (například v češtině) se pro oddělování desetinných čísel nepoužívá desetinná tečka, ale čárka. Zde je nastaveno takové nastavení kultury, aby bylo parsování nezávislé na jazyce, neboli, aby se vždy jako oddělovač používala tečka. Načítání by tak mělo fungovat na všech jazykových nastaveních.

Naše načtené objekty si můžeme následně vykreslit v metodě Draw():

foreach (Sprite s in sprites)
    spriteBatch.Draw(s.Texture, new Rectangle(s.X, s.Y, s.Texture.Width, s.Texture.Height), Color.White);

Když si spustíme hru, uvidíme, že se nám obrázky vykreslí na pozice, které byly zadány v souboru.

Věřím, že z dnešního dílu jste si odnesli mnoho zajímavých informací. Příště se podíváme, jak se dá taková hra pro Windows Phone naportovat na počítač nebo Xbox 360 a jaké tam jsou hlavní rozdíly.

Autor článku Tomáš Slavíček
Tomáš Slavíček

Kapitoly článku