Personalizzare il proprio progetto in c++ con DynRPG
1. Introduzione
Per: RPG Maker 2003
Autore: Io.
Descrizione: Visto che già in due hanno chiesto di scrivere qualcosa, spiegherò rapidamente come effettuare modifiche ai propri giochi tramite questo fantastico plugin sviluppato da Cherry.
Il funzionamento è semplice. Una volta scaricata e installata la patch è possibile creare delle dll di proprio pugno, indipendenti tra loro, che si occupano di modificare vari aspetti del gioco.
Il plugin lo potete scaricare da qui: http://rpg-maker.che...iles/dynrpg.rar
E vi consiglio di tenervi sotto mano questa pagina, con tutta la documentazione in inglese:
http://rpg-maker.che...nrpg/index.html
Due piccole precisazioni:
-Questo tutorial presuppone che voi conosciate c++, o che perlomeno lo mastichiate. Non è richiesta conoscenza di programmazione ad oggetti molto specifica, basta conoscere le basi.
-DynRPG è fortemente instabile e sperimentale. Ciò implica che non tutte le funzioni documentate funzionano come si deve e che spesso non sono utilizzabili.
Ultima modifica: 06/12/2012.
2. Installazione
Questo è un semplice riassunto/traduzione di quanto potete trovare sul sito di Cherry:
1) Create un nuovo progetto (che userete per testare il plugin) e applicate la patch che trovate qui: http://rpg-maker.che...nrpg/patch.html
2) Scaricate Code::Blocks, un IDE C++ (compilatore) basato su mingW da questa apgina: http://www.codeblock...nloads/binaries
Scaricate la versione "grossa", ovvero quella che include mingW (esempio: codeblocks-12.11mingw-setup.exe)
3) Installate Code::Blocks e tenete segnato in un angolo la directory di installazione (es: C:\Programmi\CodeBlocks\ )
4) Accedete alla cartella annotata nel passaggio precedente, entrate nella cartella MingW. Copiate in questa cartella il contenuto del file zip che avete scaricato precedentemente (solo i file che stanno nella cartella sdk). Confermate la sostituzione delle cartelle lib e include.
5) avviate code::Blocks e create un nuovo progetto, selezionando Dynamic Link Library.
6)Selezionate il nome della vostra DLL, facendo attenzione a non inserire spazi nel nome. Nella seconda casella inserite il percorso della cartella DynPlugins che si trova nella cartella del vostro gioco. Nell'ultimo field dovrebbe comparire qualcosa del genere: C:\Users\My Username\Documents\My Test Project\DynPlugins\are_you_sure\are_you_sure.cbp
7) Verificate che il compilatore predefinito sia GNU GCC Compiler
8) Deselezione la voce Debug
9) Sotto la voce "Release" options cambia Output Dir con ..\ (due punti e un backslash), quindi clicka Finish.
10) Sul menu Project seleziona Build Options..., quindi Linker Settings
11) Nella sezione linker libreries premete Add e inserite DynRPG, quindi confermare
12) Sempre nel menu Project selezionare Set programs' arguments...
13) Nella casella Host application premere il tasto a destra e seleziona il file RPG_RT.exe del vostro progetto di testing.
14) Nella casella Program Arguments inserire TestPlay ShowTitle Window
15) Nella schermata principale del programma, eliminare il file Main.h (remove file from project)
16) Sostituite il contenuto del file Main.cpp con:
#define AUTO_DLLMAIN #include <DynRPG/DynRPG.h>A questo punto possiamo iniziare a scrivere il nostro primo plugin ^^.
3. Concetti generali
Non vi spiegherò per filo e per segno come fare ogni cosa, vi spiegherò in linea di massima come funziona questo SDK.
La tipologia di programmazione su cui dovremo lavorare è, se mi consentite il termine, basata sulle callback. In sostanza il gioco richiamerà delle funzioni con dei nomi documentati nella libreria all'accadere di uno specifico evento.
Ad esempio, quando il gioco viene avviato, verrà richiamata la funzione onStartup(char *pluginName). Tutta il nostro lavoro si focalizza sulla sovrascrittura di queste funzioni, al fine di ottenere un comportamento desiderato.
Fondamentale comprendere un paio di concetti:
DynRPG supporta più DLL applicate contemporaneamente sullo stesso progetto. Nel momento in cui si verifica una condizione per cui dovrebbe essere richiamata una funzione di callback, il programma richiama TUTTE le funzioni di callback di quel tipo su ognuna delle DLL installate. Ciò significa che se si hanno 4 librerie che definiscono la funzione OnStartup, ognuna di esse sarà richiamata in ordine. Fortunatamente è possibile controllare questo comportamento. Ogni funzione callback richiede un return di tipo booleano. Se si restituisce true verrà richiamata la funzione della prossima DLL, altrimenti nessun'altra DLL potrà più controllare quella specifica condizione.
Se quindi scriviamo in questo modo:
bool onSetVariable(int id, int value) { if(MessageBox( NULL, // We don't need a window handle "Vuoi che qualche altra DLL catturi questo evento?", // Text "Rispondi con sincerità u.u", // Title MB_YESNO | MB_ICONQUESTION // Flags (yes/no buttons and question icon) ) == IDYES) { return true; } else { return false; } }Apparirà un message box nel momento in cui una variabile verrà settata ad un certo valore. Rispondendo positivamente alla domanda la stessa funzione verrà richiamata nella prossima DLL, altrimenti no.
Non tutte le funzioni di callback hanno questo comportamento, ma questo è ampiamente documentato. Ecco un elenco di tutte le possibili funzioni di callback:
http://rpg-maker.che..._callbacks.html
Ad esempio OnStartup, nel caso si restituisca il valore true il gioco proseguirà la sua esecuzione normalmente, altrimenti verrà chiuso all'avvio.
Le funzioni di callback hanno come parametri le informazioni relative all'evento che si è verificato, ma non sempre queste informazioni sono sufficienti per fare quello che ci serve. Spesso avremo quindi bisogno di accedere a informazioni e settaggi del gioco in maniera più generale.
Vi sono quindi una lista di variabili "globali", facilmente accessibili in ogni momento, che ci fanno da database ambulante.
Ad esempio RPG::actors contiene la lista dei combattenti in battaglia, RPG::map contiene le informazioni relative alla mappa attuale, RPG::variables le variabili di gioco e così via.
Ecco una lista completa:
http://rpg-maker.che...e__objects.html
Infine, altra funzione importante, è la possibilità di definire una sorta di database d'appoggio usando il file di configurazione DynRPG.ini, tramite la funzione di libreria RPG::loadConfiguration(pluginName).
Un esempio completo è presente nel prossimo capitolo.
4. Un esempio commentato
Mi accingo dunque a commentarvi un plugin d'esempio, disponibile nella sezione Getting Started della documentazione di Cherry, sperando che sia più utile di tanta teoria.
Lo scopo di questa DLL è quello di mostrare in battaglia delle icone sulla testa dei combattenti per indicarne gli status alterati.
Ecco uno screen:
Iniziamo, dunque:
#define AUTO_DLLMAIN #include <DynRPG/DynRPG.h> #include <sstream> // For std::stringstream // We store the configuration here std::map<std::string, std::string> configuration; // We store the images of the conditions here std::map<int, RPG::Image *> images; // This handler is called on startup bool onStartup(char *pluginName) { // We load the configuration from the DynRPG.ini file here configuration = RPG::loadConfiguration(pluginName); return true; // Don't forget to return true so that the start of the game will continue! }In questa prima parte non c'è gran che: includiamo la funzione di libreria sstream, che ci servirà per fare qualche elaborazione di stringhe più tardi. Definiamo 2 variabili d'appoggio, una contenente le info di configurazione (che prenderemo dal file DynRPG.ini), l'altra contenente le immagini che ci accingiamo ad usare come icone degli status. Infine definiamo il callback OnStartup, in cui carichiamo nella variabile configuration le info dal file ini. Spiegherò in fondo come andrà formattato il contenuto del file.
Entriamo ora nella funzione contenente la parte più succosa del codice, quella per mostrare le icone!.
// This handler is called after a battler is drawn bool onBattlerDrawn(RPG::Battler *battler, bool isMonster, int id) { int totalWidth = 0; // We store the total width of the condition icons here // We loop through all the elements of the battler's "conditions" array here // Note that the "condition" array is one-based, thus we start at index 1 for(int i = 1; i <= battler->conditions.size; i++) {
La funzione di callback su coi lavoriamo viene richiamata ogni frame, poco dopo aver disegnato su schermo un certo battler.
Le tre variabili d'ingresso della funzione (RPG::Battler *battler, bool isMonster, int id) ci danno informazioni riguardo a chi è appena stato disegnato. battler contiene un puntatore all'entità "combattente", isMonster ci dice se esso è un mostro o un eroe, id ci dà l'id del mostro o player che sia all'interno del database (quello con cui ci accederemmo tramite le variabili globali, in sostanza).
Definiamo una variabile d'appoggio per memorizzare la dimensione dell'icona, quindi iniziamo ad iterare tutte le possibili condizioni (status) di battaglia di quel battler.
Attenzione, come scritto nella documentazione le condizioni di battaglia sono contenute in un array che inizia il conteggio da 1, non da 0!!
// If the battler has the condition (see documentation)... if(battler->conditions[i] > 0) { // If the image isn't loaded yet... if(!images[i]) { // First, we create an RPG::Image object images[i] = RPG::Image::create(); // Yes, we want to use the transparent color images[i]->useMaskColor = true; // Now, we put the key name for the configuration entry together // It should be "Condition12" for condition 12, for example std::stringstream keyName; keyName << "Condition" << i; // Now, we try to load the image. If loading the image fails, // nothing special will happen (because of the "false" at the // "showErrors" parameter), the image will just be empty. images[i]->loadFromFile(configuration[keyName.str()], false); } // We add the image's width to the total width totalWidth += images[i]->width; } }Se il battler in questione ha lo status che sto iterando (o più precisamente, se ha quello status e il numero di turni rimanenti prima che se ne vada è > 0) allora:
Se non è la prima volta che l'immagine di questo status viene caricata:
Creo un nuovo oggetto immagine di tipo trasparente, quindi carico la grafica da un'immagine associata al nome "Condition"+IDDELLACONDIZIONE. Questo nome viene definito all'interno del file DynRPG.ini, e a fine tutorial viene spiegato come va formattato, e associa a quel nome il percorso dell'immagine da caricare.
Quindi sommo alla variabile temporanea creata prima le dimensioni dell'immagine attualmente caricata e chiudo il for.
Tutta questa parte di codice verrà eseguita sostanzialmente per caricare in una cache tutte le icone degli status nel caso sia la prima volta che vengano usate.
// Now we need to know the Y coordinate of the top of the battler int topY; if(isMonster) { // It is a monster. We just use the monster's image to find out // its size. RPG::Monster *monster = (RPG::Monster *)battler; // First, we cast the "battler" to a "Monster" topY = monster->y - monster->image->height / 2; // Now we calculate the top position } else { // Okay, since we don't have a way to find out the size of an actor's // battle graphic, we will just "guess" that it's a normal BattleCharSet // which is 48 pixels tall. // In a "good" plugin, there would be a way to set the actual height // for each actor in the configuration, but this would be too much // for this tutorial. topY = battler->y - 48 / 2; } // We will use this variable to store the current X coordinate while we // draw the images. We will increase this variable every time we draw an // image. int currentX = battler->x - totalWidth / 2; // Okay, let's loop again through the conditions. This is necessary // because we first had to find out the total width before we could
Tutta questa parte non è niente di che, semplicemente è una serie di conti per determinare le coordinate in cui posizionare le icone, in modo che non si sovrappongano e che stiano sulla testa dell'eroe. Notare che il discrimine per come posizionarle sta nel fatto che il battler attuale sia un Mostro (ovvero sta a sinistra) o un eroe. Tramite battler->x e battler->y ottengo le coordinate del battler e uso la variabile totalWidth, che rappresenta la lunghezza totale della "striscia" di icone, per centrarle per bene. Lascio a voi una lettura più accurata del codice.
// start drawing the images. Now we will draw them. for(int i = 1; i <= battler->conditions.size; i++) { // If the battler has the condition (see documentation)... if(battler->conditions[i] > 0) { // Okay, here we actually draw the image on the screen! RPG::screen->canvas->draw(currentX, topY - images[i]->height, images[i]); // And we increase the current X coordinate. currentX += images[i]->width; } }Ora, dopo aver determinato la posizione di ogni grafica e caricato il tutto in cache, ci occupiamo di disegnare effettivamente le immagini su schermo, tramite il comando RPG::screen->canvas->draw, reiterando da capo tutti gli status che affliggono il battler.
// Clear the condition cache which is normally used to determine which // conditions should be shown in the "info window" on top of the screen // during target selection (we don't need to display the conditions twice!) for(int i = 0; i < 5; i++) { battler->displayedConditions[i] = 0; } return true; // It's okay that the battler is drawn, so we return true. Don't forget that! }Infine azzerando il valore dell'attributo displayedConditions impedisco al menu di battaglia di mostrare scritto in che condizione stanno i mostri, in modo da evitare informazioni ridondanti per il giocatore.
// This handler is called when the game exits void onExit() { // We will unload all images here. // If you are not familiar with C++ iterators: This "for" loop just iterates // through all items in "images". for(std::map<int, RPG::Image *>::const_iterator iter = images.begin(); iter != images.end(); ++iter ) { // The reason we don't use images[iter->first] instead of iter->second // is that RPG::Image::destroy also takes a reference and sets the // parameter to zero at the end. RPG::Image::destroy(images[iter->first]); } }Ultima, ma non meno importante, la funzione onExit(), che verrà richiamata alla chiusura del gioco, elimina dalla memoria le immagini della cache. Se lavorate con immagini caricate da voi ricordate sempre di eliminarle non appena non vi serviranno più o, per lo meno, alla chiusura del gioco.
Ecco come dovrà essere formattato file DynRPG.ini per contenere le informazioni utili:
[condition_icons] Condition2=Picture\poison.png Condition3=Picture\blind.png Condition5=Picture\berserk.png Condition6=Picture\confused.png Condition7=Picture\sleep.pngCome titolo inseriamo il nome della DLL tra parentesi quadre, quindi definiamo dei nomi che vengono poi richiamati all'interno del codice.
Con questo ho concluso questo tutorial. Spero vi possa essere utile!
Postate qui dubbi o problemi che si presentano ;D