24-08-2015, 11:47 AM
Tutorial 25: Xml Handling
Dimostriamo il caricamento e il salvataggio delle configurazioni tramite file XML
Autore: Y.M. Bosman <yoran.bosman@gmail.com>
Questo demo ci mostra un sistema completo per la gestione delle configurazioni. Il sorgente è facilmente integrabile nelle vostre applicazioni.
Classe SettingManager.
Questa classe carica e salva le impostazioni dell'engine e gestisce le sue opzioni.
La classe fa uso di irrMap, si tratta di un array assoiciativo che usa un albero di tipo red-black per creare una mappa di chiavi, durante il percorso vedremo come usarlo.
Carichiamo il file xml dal disco, riscriviamo le impostazioni di default il file xml che andiamo a carica ha la seguente struttura, i settaggi sono annidati nel nodo principale
<?xml version="1.0"?>
<mygame>
<video>
<setting name="driver" value="Direct3D9">
<setting name="fullscreen" value="0">
<setting name="resolution" value="1024x768">
</video>
</mygame>
Contesto per l'applciativo e variabili globali
Tipico event receiver.
Funzione che crea la finestra di dialogo con cui mostriamo le impostazioni correnti, le modifichiamo e le salviamo su file xml
La funzione main. Crea tutti gli oggetti e il gestore del xml.
Versione pdf scaricabile da QUI
Dimostriamo il caricamento e il salvataggio delle configurazioni tramite file XML
Autore: Y.M. Bosman <yoran.bosman@gmail.com>
Questo demo ci mostra un sistema completo per la gestione delle configurazioni. Il sorgente è facilmente integrabile nelle vostre applicazioni.
Codice PHP:
#include <irrlicht.h>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif
Questa classe carica e salva le impostazioni dell'engine e gestisce le sue opzioni.
La classe fa uso di irrMap, si tratta di un array assoiciativo che usa un albero di tipo red-black per creare una mappa di chiavi, durante il percorso vedremo come usarlo.
Codice PHP:
class SettingManager
{
public:
// Costruttore che imposta i settaggi di default
SettingManager(const stringw& settings_file): SettingsFile(settings_file), NullDevice(0)
{
// Il device a null per Irrlicht, caricheremo i settaggi prima della creazione del device quindi ecco nulldevice
NullDevice = irr::createDevice(irr::video::EDT_NULL);
//DriverOptions trattandosi di una mappa irrlicht possiamo inserire i
//valori in due modi chiammando insert(key,value) o usando [key] operator
//il [] operator sovrascrive i valori se preesistenti
DriverOptions.insert(L"Software", EDT_SOFTWARE);
DriverOptions.insert(L"OpenGL", EDT_OPENGL);
DriverOptions.insert(L"Direct3D9", EDT_DIRECT3D9);
//opzioni dulla risoluzione
ResolutionOptions.insert(L"640x480", dimension2du(640,480));
ResolutionOptions.insert(L"800x600", dimension2du(800,600));
ResolutionOptions.insert(L"1024x768", dimension2du(1024,768));
//i nostri defaults preferiti
SettingMap.insert(L"driver", L"Direct3D9");
SettingMap.insert(L"resolution", L"640x480");
SettingMap.insert(L"fullscreen", L"0"); //0 significa false
}
// Distruttore, potremmo salvare i settings automaticamente nel exit
// del programma nel nostro caso qua droppiamo semplicemente il nulldevice
~SettingManager()
{
if (NullDevice)
{
NullDevice->closeDevice();
NullDevice->drop();
}
};
<?xml version="1.0"?>
<mygame>
<video>
<setting name="driver" value="Direct3D9">
<setting name="fullscreen" value="0">
<setting name="resolution" value="1024x768">
</video>
</mygame>
Codice PHP:
bool load()
{
//se il device fallisce non tentiamo di caricare niente
if (!NullDevice)
return false;
irr::io::IXMLReader* xml = NullDevice->getFileSystem()->createXMLReader(SettingsFile); //creiamo il lettore xml
if (!xml)
return false;
const stringw settingTag(L"setting"); //cerchiamo questo tag nel xml
stringw currentSection; //tracciamo la sezione corrente
const stringw videoTag(L"video"); //costante per il tag videotag
//finché c'è qualcosa da leggere
while (xml->read())
{
//controllo il tipo di nodo
switch (xml->getNodeType())
{
//trovo un nuovo elemento
case irr::io::EXN_ELEMENT:
{
//Sono nella zona vuota o mygame, cerco il tag video e lo rendo la sezione attiva
if (currentSection.empty() && videoTag.equals_ignore_case(xml->getNodeName()))
{
currentSection = videoTag;
}
//sono nella sezione video, trovo un setting da parserizzare
else if (currentSection.equals_ignore_case(videoTag) && settingTag.equals_ignore_case(xml->getNodeName() ))
{
//leggo la chiave
stringw key = xml->getAttributeValueSafe(L"name");
//se c'è una chiava da impostare
if (!key.empty())
{
//imposto il setting nella mappa con il suo valore,
//l'operatore [] sovrascrive i valori preesistenti o
//li inserisce come nuova chiave
SettingMap[key] = xml->getAttributeValueSafe(L"value");
}
}
//..
// Qui potete inserire la gestione dei vostri tags e sezioni
//..
}
break;
//Finito l'elemento
case irr::io::EXN_ELEMENT_END:
//Alla fine della sezione video resettiamo il tag
currentSection=L"";
break;
}
}
// droppiamo il lettore xml alla fine di tutto
xml->drop();
return true;
}
// Scriviamo sul file xml usando il nulldevice.
bool save()
{
//se il device era fallito non salviamo nulla
if (!NullDevice)
return false;
//creo il componente per scrivere l'xml
irr::io::IXMLWriter* xwriter = NullDevice->getFileSystem()->createXMLWriter( SettingsFile );
if (!xwriter)
return false;
//scrivo la parte obbligatori dell'header necessario per ogni xml
xwriter->writeXMLHeader();
//inizio con l'elemento mygame, potete cambiare "mygame" come volete
xwriter->writeElement(L"mygame");
xwriter->writeLineBreak(); //new line
//inizio la sezione con i settaggi video
xwriter->writeElement(L"video");
xwriter->writeLineBreak(); //new line
// getIterator ci da il puntatore al primo nodo della mappa
// ad ogni iterazione incremento l'iterator che ci da il nodo successivo
// finché non arrivo alla fine e scrivo i valori tramite le chiavi dei nodi
map<stringw, stringw>::Iterator i = SettingMap.getIterator();
for(; !i.atEnd(); i++)
{
//scrivo gli elementi così <setting name="key" value="x" />
//il secondo parametro da un elemento vuoto senza figli, un attributo
xwriter->writeElement(L"setting",true, L"name", i->getKey().c_str(), L"value",i->getValue().c_str() );
xwriter->writeLineBreak();
}
xwriter->writeLineBreak();
//chiudo la sezione video
xwriter->writeClosingTag(L"video");
xwriter->writeLineBreak();
//..
// Qua possiamo inseirre una sezione sound, ecc..
//..
//chiudo la sezione mygame
xwriter->writeClosingTag(L"mygame");
//drop dello scrittore xml
xwriter->drop();
return true;
}
// Impostiamo i parametri del nostro manager
void setSetting(const stringw& name, const stringw& value)
{
SettingMap[name]=value;
}
// scrittura di valori interi sulla mappa
void setSetting(const stringw& name, s32 value)
{
SettingMap[name]=stringw(value);
}
// Ricevo i valori come stringa
stringw getSetting(const stringw& key) const
{
//la funzione find restituisce il puntatore al nodo della mappa se trovo
//la chiave, altrimenti restituisce null, il nodo della mappa ha una
//funzione getValue e getKey, con la chiave restituiamo node->getValue()
map<stringw, stringw>::Node* n = SettingMap.find(key);
if (n)
return n->getValue();
else
return L"";
}
//
bool getSettingAsBoolean(const stringw& key ) const
{
stringw s = getSetting(key);
if (s.empty())
return false;
return s.equals_ignore_case(L"1");
}
//
s32 getSettingAsInteger(const stringw& key) const
{
//cast implicito di stringa invece di stringw perché strtol10 non accetta stringe troppo larghe
const stringc s = getSetting(key);
if (s.empty())
return 0;
return strtol10(s.c_str());
}
public:
map<stringw, s32> DriverOptions; //opzione per il config del driver
map<stringw, dimension2du> ResolutionOptions; //opzione per la risoluzione
private:
SettingManager(const SettingManager& other); // solo definitoo
SettingManager& operator=(const SettingManager& other); // solo definito
map<stringw, stringw> SettingMap; //config corrente
stringw SettingsFile; // posizione del file, tipicamente il
irr::IrrlichtDevice* NullDevice;
};
Codice PHP:
struct SAppContext
{
SAppContext()
: Device(0),Gui(0), Driver(0), Settings(0), ShouldQuit(false),
ButtonSave(0), ButtonExit(0), ListboxDriver(0),
ListboxResolution(0), CheckboxFullscreen(0)
{
}
~SAppContext()
{
if (Settings)
delete Settings;
if (Device)
{
Device->closeDevice();
Device->drop();
}
}
IrrlichtDevice* Device;
IGUIEnvironment* Gui;
IVideoDriver* Driver;
SettingManager* Settings;
bool ShouldQuit;
//settings dialog
IGUIButton* ButtonSave;
IGUIButton* ButtonExit;
IGUIListBox* ListboxDriver;
IGUIListBox* ListboxResolution;
IGUICheckBox* CheckboxFullscreen;
};
Codice PHP:
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(SAppContext & a) : App(a) { }
virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT )
{
switch ( event.GUIEvent.EventType )
{
//gestione pressione bottoni
case EGET_BUTTON_CLICKED:
{
//Salviamo tutto se premuto il tasto salva
if ( event.GUIEvent.Caller == App.ButtonSave )
{
//se c'è una selezione la scriviamo
if ( App.ListboxDriver->getSelected() != -1)
App.Settings->setSetting(L"driver", App.ListboxDriver->getListItem(App.ListboxDriver->getSelected()));
//se c'è una selezione la scriviamo
if ( App.ListboxResolution->getSelected() != -1)
App.Settings->setSetting(L"resolution", App.ListboxResolution->getListItem(App.ListboxResolution->getSelected()));
App.Settings->setSetting(L"fullscreen", App.CheckboxFullscreen->isChecked());
if (App.Settings->save())
{
App.Gui->addMessageBox(L"settings save",L"settings saved, please restart for settings to change effect","",true);
}
}
// click sul pulsante cancel/exit, esco dall'applicazione
else if ( event.GUIEvent.Caller == App.ButtonExit)
{
App.ShouldQuit = true;
}
}
break;
}
}
return false;
}
private:
SAppContext & App;
};
Codice PHP:
void createSettingsDialog(SAppContext& app)
{
// togliamo l'alpha dalla gui
for (irr::s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
irr::video::SColor col = app.Gui->getSkin()->getColor((irr::gui::EGUI_DEFAULT_COLOR)i);
col.setAlpha(255);
app.Gui->getSkin()->setColor((irr::gui::EGUI_DEFAULT_COLOR)i, col);
}
//creo la finestra per le impostazioni video
gui::IGUIWindow* windowSettings = app.Gui->addWindow(rect<s32>(10,10,400,400),true,L"Videosettings");
app.Gui->addStaticText (L"Select your desired video settings", rect< s32 >(10,20, 200, 40), false, true, windowSettings);
// aggiungo la listbox per la scelta dei driver
app.Gui->addStaticText (L"Driver", rect< s32 >(10,50, 200, 60), false, true, windowSettings);
app.ListboxDriver = app.Gui->addListBox(rect<s32>(10,60,220,120), windowSettings, 1,true);
//aggiungo le opzioni disponibili per la scelta del driver selezionato
map<stringw, s32>::Iterator i = app.Settings->DriverOptions.getIterator();
for(; !i.atEnd(); i++)
app.ListboxDriver->addItem(i->getKey().c_str());
//imposto il driver selezionato
app.ListboxDriver->setSelected(app.Settings->getSetting("driver").c_str());
// aggiungo la listbox per la scelta della risoluzione
app.Gui->addStaticText (L"Resolution", rect< s32 >(10,130, 200, 140), false, true, windowSettings);
app.ListboxResolution = app.Gui->addListBox(rect<s32>(10,140,220,200), windowSettings, 1,true);
//aggiungo le opzioni per la risoluzione scelta
map<stringw, dimension2du>::Iterator ri = app.Settings->ResolutionOptions.getIterator();
for(; !ri.atEnd(); ri++)
app.ListboxResolution->addItem(ri->getKey().c_str());
//imposto la risoluzione selezionata
app.ListboxResolution->setSelected(app.Settings->getSetting("resolution").c_str());
//aggiungo la checkbox per il fullscreen
app.CheckboxFullscreen = app.Gui->addCheckBox(
app.Settings->getSettingAsBoolean("fullscreen"),
rect<s32>(10,220,220,240), windowSettings, -1,
L"Fullscreen");
//alla fine il pulsante salva
app.ButtonSave = app.Gui->addButton(
rect<s32>(80,250,150,270), windowSettings, 2,
L"Save video settings");
//pulsante exit/cancel
app.ButtonExit = app.Gui->addButton(
rect<s32>(160,250,240,270), windowSettings, 2,
L"Cancel and exit");
}
Codice PHP:
int main()
{
//creo un nuovo contesto per l'applicazione
SAppContext app;
//creo i parametri per la creazione del device che potranno essere sovrascritte
SIrrlichtCreationParameters param;
param.DriverType = EDT_SOFTWARE;
param.WindowSize.set(640,480);
// Provo a caricare il config. Lascio come esercizio il salvataggio del file
// nella cartella locale dell'applicativo, il posto più logico dove salvarlo
// per il gioco. Per gli altri sistemi operativi vi rimando al manuale
app.Settings = new SettingManager("../../media/settings.xml");
if ( !app.Settings->load() )
{
// ...
// Qui possiamo aggiungere le nostre eccezioni, per il momento continuiamo perché abbiamo i default nel costruttore del SettingManager
// ...
}
else
{
//carico dal disco i settaggi dal file xml
//mappo i setting sul driver e testo che siano validi il DriverOptions
//contiene la rappresentazione in stringa mappata sul E_DRIVER_TYPE enum
//e.g "direct3d9" diventa un 4, guardate la classe DriverOptions per
//maggiori dettagli
map<stringw, s32>::Node* driver = app.Settings->DriverOptions.find( app.Settings->getSetting("driver") );
if (driver)
{
if ( irr::IrrlichtDevice::isDriverSupported( static_cast<E_DRIVER_TYPE>( driver->getValue() )))
{
// il driver selezionato è supportato, quindi lo uso.
param.DriverType = static_cast<E_DRIVER_TYPE>( driver->getValue());
}
}
//impostazioni della risoluzione, come fatto sopra per il video
map<stringw, dimension2du>::Node* res = app.Settings->ResolutionOptions.find( app.Settings->getSetting("resolution") );
if (res)
{
param.WindowSize = res->getValue();
}
//vado in fullscreen
param.Fullscreen = app.Settings->getSettingAsBoolean("fullscreen");
}
//creo il device irrlicht dai settaggi
app.Device = createDeviceEx(param);
if (app.Device == 0)
{
// Aggiungete la gestione delle eccezioni nel caso di fallimento del driver
exit(0);
}
app.Device->setWindowCaption(L"Xmlhandling - Irrlicht engine tutorial");
app.Driver = app.Device->getVideoDriver();
app.Gui = app.Device->getGUIEnvironment();
createSettingsDialog(app);
//imposto l'event receiver in modo da rispondere agli eventi della gui
MyEventReceiver receiver(app);
app.Device->setEventReceiver(&receiver);
//entriamo nel main loop
while (!app.ShouldQuit && app.Device->run())
{
if (app.Device->isWindowActive())
{
app.Driver->beginScene(true, true, SColor(0,200,200,200));
app.Gui->drawAll();
app.Driver->endScene();
}
app.Device->sleep(10);
}
//la nostra app distruggerà il device nel suo distruttore
return 0;
}
Versione pdf scaricabile da QUI