Creare videogiochi con java (parte 3)
Terzo di una serie di articoli sulle basi della libreria GTGE, una libreria java completamente gratuita per creare videogiochi 2d portabili e di impatto
Eccoci finalmente alla terza parte di questa miniguida a GTGE. In questo appuntamento vedremo come gestire le collisioni all’interno del nostro videogioco. Il sistema di gestione delle collisioni presente in GTGE è molto semplice (e allo stesso tempo flessibile) e non richiede continui controlli sulla posizione degli Sprite e sulle loro dimensioni. Il tutto si basa sul concetto di gruppo.
Grazie ai gruppi è possibile tenere separati i diversi elementi del nostro gioco semplificandone la gestione e alleggerendo il codice. Vediamo prima un pezzo di codice che non fa uso di gruppi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import java.awt.*; import com.golden.gamedev.*; import com.golden.gamedev.object.*; import com.golden.gamedev.object.background.*; public class MyGame extends Game { Background background; Sprite s1, s2, s3, s4, s5, s6, s7, s8, s9; public void initResources() { background = new ColorBackground(Color.BLUE; s1 = new Sprite(getImage("player.png")); s2 = new Sprite(getImage("player.png")); s3 = new Sprite(getImage("player.png")); s4 = new Sprite(getImage("player.png")); s5 = new Sprite(getImage("player.png")); s6 = new Sprite(getImage("player.png")); s7 = new Sprite(getImage("player.png")); s8 = new Sprite(getImage("player.png")); s9 = new Sprite(getImage("player.png")); s1.setBackground(background); s2.setBackground(background); s3.setBackground(background); s4.setBackground(background); s5.setBackground(background); s6.setBackground(background); s7.setBackground(background); s8.setBackground(background); s9.setBackground(background); } public void update(long elapsedTime) { background.update(elapsedTime); s1.update(elapsedTime); s2.update(elapsedTime); s3.update(elapsedTime); s4.update(elapsedTime); s5.update(elapsedTime); s6.update(elapsedTime); s7.update(elapsedTime); s8.update(elapsedTime); s9.update(elapsedTime); } public void render(Graphics2D g) { background.render(g); s1.render(g); s2.render(g); s3.render(g); s4.render(g); s5.render(g); s6.render(g); s7.render(g); s8.render(g); s9.render(g); } public static void main(String[] args) { GameLoader game = new GameLoader(); game.setup(new MyGame(), new Dimension(640,480), false); game.start(); } } |
Questo codice non fa altro che creare 9 Sprite e mostrarli a schermo. Se gli Sprite fossero in numero molto maggiore la situazione sarebbe difficile da gestire. Per questo si utilizzano gruppi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import java.awt.*; import com.golden.gamedev.*; import com.golden.gamedev.object.*; import com.golden.gamedev.object.background.*; public class MyGame extends Game { Background background; SpriteGroup PLAYER_GROUP; public void initResources() { background = new ColorBackground(Color.BLUE); PLAYER_GROUP = new SpriteGroup("Player Group"); PLAYER_GROUP.setBackground(background); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); PLAYER_GROUP.add(new Sprite(getImage("player.png"))); } public void update(long elapsedTime) { background.update(elapsedTime); PLAYER_GROUP.update(elapsedTime); } public void render(Graphics2D g) { background.render(g); PLAYER_GROUP.render(g); } public static void main(String[] args) { GameLoader game = new GameLoader(); game.setup(new MyGame(), new Dimension(640,480), false); game.start(); } } |
I due programmi fanno la stessa cosa ma il secondo è più compatto e meno prolisso del primo. Il tutto si riduce a lavorare sui gruppi e non più sugli Sprite: aggiungiamo gli Sprite ad un gruppo ed anzichè dover aggiornare ogni singolo Sprite aggiorniamo il gruppo in cui si trova. In questo modo il codice di update e render diventa più leggibile e facilmente mantenibile.
I maggiori vantaggi nell’utilizzo dei gruppi si hano quando se ne utilizzano diversi. Ovviamente i vari gruppi devono essere creati con criterio, non a casaccio. Ad esempio, potremmo creare un gruppo per il personaggio, un gruppo per gli elementi a livello del terreno, un gruppo per i nemici, un gruppo per gli elementi sopra il terreno e così via, come in questo esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import java.awt.*; import com.golden.gamedev.*; import com.golden.gamedev.object.*; import com.golden.gamedev.object.background.*; public class MyGame extends Game { Background background; SpriteGroup PLAYER_GROUP, PLAYER_SHOT_GROUP, ENEMY_GROUP, ENEMY_SHOT_GROUP, ASTEROID_GROUP, BONUS_GROUP; public void initResources() { background = new ColorBackground(Color.BLUE); PLAYER_GROUP = new SpriteGroup("Player Group"); PLAYER_SHOT_GROUP = new SpriteGroup("Player Shot Group"); ENEMY_GROUP = new SpriteGroup("Enemy Group"); ENEMY_SHOT_GROUP = new SpriteGroup("Enemy Shot Group"); ASTEROID_GROUP = new SpriteGroup("Asteroid Group"); BONUS_GROUP = new SpriteGroup("Bonus Group"); PLAYER_GROUP.setBackground(background); PLAYER_SHOT_GROUP.setBackground(background); ENEMY_GROUP.setBackground(background); ENEMY_SHOT_GROUP.setBackground(background); ASTEROID_GROUP.setBackground(background); BONUS_GROUP.setBackground(background); } public void update(long elapsedTime) { background.update(elapsedTime); PLAYER_GROUP.update(elapsedTime); PLAYER_SHOT_GROUP.update(elapsedTime); ENEMY_GROUP.update(elapsedTime); ENEMY_SHOT_GROUP.update(elapsedTime); ASTEROID_GROUP.update(elapsedTime); BONUS_GROUP.update(elapsedTime); } public void render(Graphics2D g) { background.render(g); PLAYER_GROUP.render(g); PLAYER_SHOT_GROUP.render(g); ENEMY_GROUP.render(g); ENEMY_SHOT_GROUP.render(g); ASTEROID_GROUP.render(g); BONUS_GROUP.render(g); } public static void main(String[] args) { GameLoader game = new GameLoader(); game.setup(new MyGame(), new Dimension(640,480), false); game.start(); } } |
Perchè utilizzare tutti questi gruppi? Semplice, ci facilitiamo la gestione delle collisioni!
In GTGE infatti le collisioni vengono rilevate tra gruppi. In questo modo la gestione diventa semplice. Ci sono diverse classi di collisioni, ognuna adatta a rilevare un certo tipo di collisione:
- BasicCollisionGroup controlla solo se la collisione c’è stata o meno
- CollisionGroup controlla se la collisione c’è stata o meno e fornisce qualche informazione supplementare
- PreciseCollisionGroup da utilizzare quando occorre sapere con precisione la posizione degli Sprite e altre informazioni
- AdvanceCollisionGroup è un sistema avanzato di rilevamento, registra collisioni verso diversi Sprite contemporaneamente e con molta precisione
- CollisionBounds registra le collisioni coi i bordi dello schermo
Ma come si gestiscono le collisioni? Molto semplice, basta estendere la classe di collisione che ci interessa e dare un corpo al metodo collide.
1 2 3 4 5 6 7 8 9 10 |
import com.golden.gamedev.object.*; import com.golden.gamedev.object.collision.*; public class MyCollision extends BasicCollisionGroup { public void collided(Sprite s1, Sprite s2) { // ... codice di gestione della collisione } } |
Dopo di che dobbiamo mettere in ascolto il rilevatore di collisioni:
1 2 3 4 5 6 7 |
CollisionManager collisionType = new MyCollision(); collisionType.setCollisionGroup(GRUPPO_1, GRUPPO_2); ... collisionType.checkCollision(); |
Ecco il codice completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import java.awt.*; import com.golden.gamedev.*; import com.golden.gamedev.object.*; import com.golden.gamedev.object.background.*; import com.golden.gamedev.object.collision.*; public class MyGame extends Game { Background background; SpriteGroup PLAYER_SHOT_GROUP, SpriteGroup ENEMY_GROUP; CollisionManager collisionType; public void initResources() { background = new ColorBackground(Color.BLUE); PLAYER_SHOT_GROUP = new SpriteGroup("Player Shot Group"); ENEMY_GROUP = new SpriteGroup("Enemy Group"); collisionType = new MyCollision(); collisionType.setCollisionGroup( PLAYER_SHOT_GROUP, ENEMY_GROUP); } public void update(long elapsedTime) { background.update(elapsedTime); PLAYER_SHOT_GROUP.update(elapsedTime); ENEMY_GROUP.update(elapsedTime); collisionType.checkCollision(); } public void render(Graphics2D g) { background.render(g); PLAYER_SHOT_GROUP.render(g); ENEMY_GROUP.render(g); } public static void main(String[] args) { GameLoader game = new GameLoader(); game.setup(new MyGame(), new Dimension(640,480), false); game.start(); } } class MyCollision extends BasicCollisionGroup { public void collided(Sprite s1, Sprite s2) { // fa sparire i 2 sprite che si sono scontrati s1.setActive(false); s2.setActive(false); } } |
Se utilizziamo molti (troppi!) gruppi il codice potrebbe diventare presto illeggibile. Si può risolvere questo problema creando gruppi di gruppi. La classe PlayField serve proprio a questo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import java.awt.*; import com.golden.gamedev.*; import com.golden.gamedev.object.*; import com.golden.gamedev.object.background.*; import com.golden.gamedev.object.collision.*; public class MyGame extends Game { PlayField playfield; Background background; SpriteGroup PLAYER_GROUP, ENEMY_GROUP; public void initResources() { background = new ColorBackground(Color.BLUE, 1024, 768); playfield = new PlayField(background); PLAYER_GROUP = playfield.add( new SpriteGroup("Player Group")); ENEMY_GROUP = playfield.add( new SpriteGroup("Enemy Group")); PLAYER_GROUP.add( new Sprite(getImage("player.png"))); PLAYER_GROUP.add( new Sprite(getImage("player.png"))); playfield.addCollisionGroup( PLAYER_GROUP, ENEMY_GROUP,new MyCollision()); } public void update(long elapsedTime) { playfield.update(elapsedTime); } public void render(Graphics2D g) { playfield.render(g); } public static void main(String[] args) { GameLoader game = new GameLoader(); game.setup(new MyGame(), new Dimension(640,480), false); game.start(); } } class MyCollision extends BasicCollisionGroup { public void collided(Sprite s1, Sprite s2) { s1.setActive(false); s2.setActive(false); } } |
E con questo si concludere questa miniguida alle basi della libreria GTGE. Spulciando tra la documentazione (formato javadoc) troverete tante informazioni utili per creare facilmente i vostri giochi mentre sul sito ufficiale del progetto sono presenti diversi giochi già pronti liberamente scaricabili. Buon lavoro!