Creare un videogioco può essere considerata una ricerca di algoritmi sempre più elaborati ed efficienti per rendere l'esecuzione del programma fluida e veloce. Oggi i videogiochi non sono più progettati e realizzati da un singolo programmatore, come avveniva qualche tempo fa, ma bensì da un'équipe, questo per suddividere i compiti e quindi ciascun programmatore può dedicarsi pienamente nel settore assegnatogli. Nonostante tutto, è sempre utile per chi incomincia a programmare giochi (e non) scrivere da solo un programma per farsi (come si dice in gergo) "le ossa".
Organizzazione di un gioco
Come detto prima il programmatore di videogiochi deve scrivere un codice efficiente. Java fortunatamente è un linguaggio ad oggetti per cui possiamo sfruttare al meglio questa caratteristica per realizzare i giochi in modo funzionale.
Per prima cosa dobbiamo introdurre il concetto di Sprite.
Sprite: piccoli oggetti mobili che si trovano all'interno dello scenario di un videogame.
Dalla definizione è evidente che i personaggi, gli spari del gioco sono tutti degli sprite con caratteristiche diverse per ciascun tipo (esempio: lo sprite del giocatore sarà diverso da quello del nemico in quanto avrà come proprietà un numero di vite...) tutti però avranno delle caratteristiche comuni come la posizione, la dimensione e l'immagine caratteristica.
Evidente che all'interno di un videogioco ci sia una gerarchia di classi la cui genitrice sarà la nostra classe Sprite generale da cui deriveremo tutte le altre classi come riportato nello schema.
Figura 1 - la classe Sprite genitrice delle altre classi. Notare come dal generale si va al particolare (tecnica top-down). Il concetto è: IN UN GIOCO TUTTO È UNO SPRITE.
Preso atto di ciò andiamo ad implementare il tutto in Java.
import java.awt.*;
public class Sprite
{
private Image i; // L'immagine associata allo sprite
private int x; // La posizione fisica dello sprite
private int y;
private int larghezza; // Le dimensioni dello sprite
private int altezza;
/*
* Costruttore dello Sprite
* Gli passo l'immagine dello sprite, la posizione e l'altezza
*/
public Sprite (Image i, int x, int y, int larghezza, int altezza)
{
this.x = x;
this.y = y;
this.i = i;
this.larghezza = larghezza;
this.altezza = altezza;
}
public void setLarghezza(int i)
{
larghezza = i;
}
public void setAltezza(int i)
{
altezza = i;
}
public Image getImage() // Ritorna l'immagine associata allo sprite
{
return i;
}
public int getLarghezza()
{
return larghezza;
}
public int getAltezza()
{
return altezza;
}
public void setSize(int altezza, int larghezza)
{
this.larghezza = larghezza;
this.altezza = altezza;
}
// ........... altri metodi set e get per le proprietà dell'oggetto
} // ~fine classe sprite
Ora ci manca un metodo importantissimo per terminare la classe: la collisione tra sprite. Ipotizziamo un gioco tipo SpaceCommander dove una navicella deve sparare a degli UFO, dobbiamo controllare che se i due oggetti si scontrano la navicella e l'UFO muore. Per fare ciò dobbiamo controllare per ciascun vertice dello sprite se esso è all'interno di un altro sprite.
In questo esempio useremo 4 vertici perché esso offre un risultato abbastanza buono dato che usiamo delle immagini rettangolari.
Di conseguenza lo sprite rettangolare risulta avere queste coordinate:
Preso atto di ciò andiamo a costruirci le varie situazioni in cui vi sono delle collisioni:
| Figura | Descrizione | |--------|-------------|
|
Figura 2A
| Lo sprite corrente (quello in esame) è entrato con l'angolo superiore sinistro nell'altro sprite di conseguenza c'è stata una collisione. |
|
Figura 2B
| Lo Sprite Corrente è entrato in collisione con l'angolo superiore destro: Collisione avvenuta |
|
Figura 2C
| Collisione con l'angolo inferiore sinistro |
|
Figura 2D
| Collisione con l'angolo inferiore destro |
Tabella 1: schema riassuntivo delle varie collisioni possibili
Implementazione in Java:
public boolean isCollisione(Sprite s)
{
/*
* L'angolo superiore sinistro coppia (x,y) è compreso nello sprite
*/
if (((this.x >= s.x) && (this.x <= s.x + s.larghezza))
&&
((this.y >= s.y) && (this.y <= s.y + s.altezza))) return true;
// angolo superiore destro
if (((x + larghezza >= s.x) && (x + larghezza <= s.x + s.larghezza)) &&
((y >= s.y) && (y <= s.y + s.altezza))) return true;
// angolo inferiore sinistro
if (((x >= s.x) && (x <= s.x + s.larghezza)) &&
((y + altezza >= s.y) && (y + altezza <= s.y + s.altezza))) return true;
// angolo inferiore destro
if (((x + larghezza >= s.x) && (x + larghezza <= s.x + s.larghezza)) &&
((y + altezza >= s.y) && (y + altezza <= s.y + s.altezza))) return true;
/* se non si sono verificate le condizioni sopra allora significa che
* non c'è stata collisione e quindi ritorno false
*/
return false;
}
Ora la nostra classe generale è terminata se successivamente avremo bisogno di avere un oggetto all'interno del nostro gioco più specifico basterà estenderla.
L'utilità di questa gerarchia risulta utile durante il controllo delle collisioni: estendiamo 2 classi da Sprite Giocatore e Nemico:
class Giocatore extends Sprite
{
private int vite;
private int energia;
// ........
}
class Nemico extends Sprite
{
private int tipo; // esempio 1 = mostro 2 = alieno 3 = ufo ..
// ........
}
Istanziamo ora 2 oggetti uno per Nemico e uno per Giocatore:
Giocatore g = new Giocatore(.....);
Nemico n = new Nemico(.....);
Ora effettuiamo il controllo delle collisioni in questo modo:
g.isCollisione(n);
Questa istruzione risulta valida in quanto anche se n è di tipo Nemico prima di tutto è un oggetto Sprite.
Il Doppio Buffer
Se qualcuno ha già provato a fare qualcosa di grafico tipo un testo scorrevole, avrà sicuramente notato che se scriviamo e cancelliamo continuamente un testo esso lampeggia o come molti dicono "fa lo sfarfallio" questo perché scrivere nella memoria video direttamente è lento. La soluzione che nel tempo si è andata affermandosi è il cosiddetto Doppio Buffer o double buffering la filosofia di questo metodo è:
Creiamo in memoria un buffer video virtuale e apportiamo a questo tutto quello vorremmo fare sulla memoria video e alla fine copiamo tutto il buffer nella memoria.
In Java la sua implementazione è molto facile (a differenza di altri linguaggi di programmazione quali C/C++, Pascal...) e permette di eliminare totalmente lo sfarfallio.
1. Per prima cosa creiamo un oggetto Image vuoto che sarà il nostro buffer virtuale 2. Creiamo un oggetto di tipo Graphics che è l'oggetto grafico del buffer virtuale 3. Usiamo l'oggetto Graphics per disegnare cancellare nel buffer virtuale 4. Copiamo l'oggetto Image nella memoria video
import java.awt.*;
import java.applet.*;
class Prova extends Applet
{
Image buffervirtuale;
Graphics b_graph;
public void init()
{
// ......
// creo un'immagine vuota della grandezza di tutto l'applet
buffervirtuale = createImage(this.getSize().width, this.getSize().height);
// prendo l'oggetto Graphics del buffer per poter apportare modifiche
// al buffer
b_graph = buffervirtuale.getGraphics();
b_graph.drawString(10, 10, "ciao Mondo!");
repaint(); // forzo l'aggiornamento dell'applet
}
public void paint(Graphics g)
{
update(g); // uso update perché aggiorno tutto l'applet
}
public void update(Graphics g)
{
// disegno tutto quello che c'è nel buffervirtuale e così elimino lo sfarfallio
g.drawImage(0, 0, buffervirtuale);
}
} // ~ Fine class Prova
Conclusione
È possibile realizzare mediante Java videogiochi di qualsiasi tipo (o quasi) implementando soluzioni e algoritmi sempre più efficienti dato che il linguaggio offre queste opportunità ma l'arma che non dobbiamo trascurare è la nostra fantasia.
MokaByte®
è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi
registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione
anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it