:: Samashooter ::
Questa pagina ospita appunti, linee guida etc. relative allo sviluppo del Samashooter.
L'organizzazione più adatta sembra essere questa:
dir progetto/ data/ scripts/ lib/ shooter.jar
dove:
Le librerie necessarie sono tutte quelle della directory lib di Slick, e js.jar di Rhino. In particolare, occorre mettere nella directory lib anche tutti i files nativi di Slick, ovvero le .dll o le .so che servono per .
Se si crea un progetto (parlo per Netbeans, ma credo che con Eclipse la faccenda sia simile) si può creare la cartella lib, e specificare di andare a pescare tutte le librerie da dentro lì. Inoltre, quando si sceglie di compilare l'intero progetto, Netbeans provvederà automaticamente a copiare la cartella lib nella cartella dist, così da poter essere copiata dove si vuole.
A questo punto, è possibile eseguire il gioco così:
java -Djava.library.path=./lib -jar shooter.jar
e siamo tutti felici.
Si chiama PRUEngine perché è nato cercando di snasare la Patty in modo virtuale, ma altro non è che un'interfaccia miniminiminimale una cui implementazione va esportata in JS:
public interface PRUEngine { public void Esegui(String s); public String PercorsoApplicazione(); }
Occorre poi una classe che implementi PRUEngine, e che abbia qualche collegamento con il motore Rhino:
La necessità di questo secondo comando è presto detta:
this.img = new Image(PRUEngine.PercorsoApplicazione + "/Data/Images/Sama.png")
Per bindare una classe in JS, vedi la pagina JavaScript in Java.
Slick ha già pensato a dare un'interfaccia generica, che ho scoperto andare bene praticamente per tutto, e può essere estesa facilmente. Essa si compone dei metodi init, update e render, ai quali aggiungerei checkCollision e destroy:
public void init(GameContainer c); public void update(GameContainer c, int delta); public void render(GameContainer c, Graphics g); public boolean checkCollision(Shape s); public void destroy();
Eccoli spiegati:
Tutte le cose, ovvero l'intero gioco, i livelli, i layer, i singoli sprite e così via, possono implementare questa interfaccia. Il vivente primordiale, cioè il gioco, propagherà la chiamata init, update o render a tutti i suoi sottoviventi, e così via in modo ricorsivo.
Esempio:
LivelloCespugli.update = function(container, delta) { for each (p in this.ListaLayer) { p.update(container, delta) } }
e via ricorrendo. Idem per render e per init.
Ogni singolo livello, nella sua init, si occupa di caricare le proprie immagini. Le immagini degli sprite, ad esempio, vengono caricate una volta sola dal livello, e poi saranno passate come parametro ai vari sprite che vi faranno ricorso.
this.sprite = new SpriteSheet(path + "/data/spritepatty.png", 200, 173) var p = new parabolicPatty(this.sprite, this.counter, this) this.ListaPatty[this.counter] = p
In questo modo si evita di caricare la roba mille volte.
Un vivente, finita la sua vita utile, deve poter informare il suo proprietario che non ha più nessuna ragione di vivere.
Nell'esempio qui sopra, passo il this.counter alla Patty, così che essa sa il proprio ID all'interno della ListaPatty. Quando poi la singola Patty decide di uscire di scena, chiamerà il metodo deleteID del layer cui appartiene. Questo metodo è così scritto:
LayerPatty1.deleteID = function(id) { delete this.ListaPatty[id] }
Avendo passato anche un this alla Patty, essa può salvare via il proprio owner, e quando vuole scomparire fa questa chiamata:
this.owner.deleteID(this.ID)
Un layer, quindi, rispetto al vivente generico, se ospita sprite dinamici deve implementare anche questo metodo.
Se si vuole evitare di dover forzare una determinata risoluzione dello schermo, non si possono usare i singoli pixel come riferimenti assoluti. Esempio: il punto (200,200) in uno schermo di 800x600 sarà più vicino al centro rispetto allo stesso punto in uno schermo (1024x768). Slick però prende come parametro i pixel.
La mia idea è che i singoli sprite ragionano in pixel: vai da qui a là e poi muori, per fare un esempio. Questi parametri gli vengono passati da chi li gestisce, e sarà lui a smazzarsi le diverse risoluzioni. Le dimensioni vengono salvate in float che vanno da 0.0 a 1.0, e poi moltiplicate per la risoluzione attuale dello schermo.
Se stabiliamo ad esempio che una certa immagine deve essere larga il 25% dello schermo, possiamo dire così:
this.Scale = (container.getHeight() / 4) / this.immagine.getHeight()
e poi fare
this.immagine.draw(this.x, this.y, this.Scale)
NOTA: la scala è un valore che viene applicato a tutte le dimensioni dell'oggetto. C'è però il problema che il rapporto tra i lati dello schermo non è omogeneo. Ad esempio, 800/600 = 1.3333, 1280/1024 = 1.25 e così via, e poi ci sono gli schermi panoramici etc. etc. Occorre trovare un modo standard per gestire tutti questi aspetti, incluso anche il fregarsene e fissare 1024x768 e via:)