Visto l’avvicinarsi della sessione estiva d’esame, e visto che in questa sessione ci sarà l’esame di Programmazione in Java e gestione della grafica, ho voluto preparare un piccolo gioco, molto semplice, per mostrare come possa essere “elaborata” la realizzazione di un progetto per un esame usando anche solamente strutture elementari semplici.
Per la costruzione del gioco ho trascurato completamente ogni legge fisica se non quella riguardante il moto rettilineo uniforme, ovvero:
$$ \Delta \vec s = \vec v \cdot \Delta t $$
Ma vediamo meglio come è costruito il gioco:
Un giocatore, quindi è rappresentato da un Character molto semplice, un tondo bianco con un cannoncino. In base al movimento del mouse questo cannoncino punta nella stessa direzione in cui punta il mouse.
Quando il giocatore preme il tasto sinistro del mouse, il cannoncino spara una raffica di proiettili (esattamente uno ogni 150ms).
L’ambiente in cui il personaggio si muove è abbastanza spartano. Uno sfondo nero.
I “nemici” sono dei rettangoli colorati.
Quando i proiettili colpiscono questi rettangoli, questi diminuiscono di dimensione, fino a sparire. Se si smette di colpire un nemico questo pian piano ripende vita, fino a raggiungere una dimensione massima.
Quando invece un nemico viene completamente abbattuto, se ne viene a creare un’altro in un’altra posizione dell’ambiente di gioco.
Il gioco termina quando il giocatore ha totalizzato 100 punti.
Ora, questo gioco è davvero molto elementare.
Un personaggio, una serie di nemici fissi in determinati punti dell’area di gioco, la capacità del personaggio di sparare dei proiettili.
Se lo si volesse completare con strutture più elaborate, si potrebbe pensare ad un numero di proiettili finito, ad una serie di potenziatori per il personaggio, come uno Shield che gli permetta di distruggere un nemico al solo tocco, oppure un potenziatore Bullet che fornisca il personaggio di proiettili più efficaci, o che rimpingui il numero di caricatori del giocatore.
Si potrebbero realizzare varie classi di nemici, più o meno facili da distruggere.
Si potrebbe implementare una Skill per il personaggio, in modo da poterne potenziale le caratteristiche durante lo svolgimento di gioco (ad esempio di potrebbe impostare un fattore di precisione per i colpi, che pian piano che il giocatore sale di livello, permette di colpire i nemici con più precisione di quanta se ne aveva inizialmente).
Un gioco può essere pensato “semplice” e completato con una miriade di features che ne complichino la struttura e lo rendano più divertente.
Ovviamente il tutto varia in funzione di chi vuole realizzare questo gioco.
Non si può pensare di progettare una cosa molto complessa se poi non si ha idea di come poterla sviluppare per renderla complessa quanto si vuole.
Per chi volesse divertirsi, metto a disposizione l’eseguibile del gioco (dovrebbe funzionare a tutti, sia su Mac che su Win, in caso ci siano problemi comunicatemelo): Shoot Em Up
Infine, per i curiosi, ecco il codice del gioco, suddiviso per le classi che ho scritto.
Non ho commentato molto, soprattutto per evitare di farvi confondere le idee, anche se per buona parte il codice dovrebbe essere comprensibile.
Quando vedremo queste strutture in laboratorio potremmo approfondire meglio eventuali dubbi che possono sorgervi ora.
SEUGame.java
1 2 3 4 5 6 7 | public class SEUGame { public static void main(String[] argv) { new SEUFrame(); } } |
SEUFrame.java
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; public class SEUFrame extends JPanel implements Runnable { public static final int P_SIZE = 50; private JFrame f = null; private Ellipse2D player = null; private Point position = null; private AffineTransform afplayer = null; private Shape character = null; private ArrayList<Bullet> bullets = null; private ArrayList<Enemy> enemies = null; private double point = 0; private MouseAdapter ma = null; private MouseMotionAdapter mma = null; private KeyAdapter ka = null; private Point mouse = null; private Point mouseCoords = null; private Thread t = null; private String istruzioni = "SHOOT EM UP\n\n" + "Il gioco consiste di semplici regole:\n" + "Colpisci i quadrati che si allargano e prenderai dei punti\n" + "Muovi il personaggio verde usando i tasti freccia sulla tua tastiera\n" + "e usa il mouse per sparare." + "\n\nIl gioco termina quando avrai totalizzato 100 punti" + "Clicca con il mouse per iniziare"; public SEUFrame() { super(); //Call JPanel Constructor position = new Point(300-P_SIZE/2, 300-P_SIZE/2); afplayer = new AffineTransform(); afplayer.translate(position.x, position.y); player = new Ellipse2D.Double(0, 0, P_SIZE, P_SIZE); character = afplayer.createTransformedShape(player); enemies = new ArrayList<Enemy>(10); for (int i=0; i<10; i++) { enemies.add(new Enemy()); } bullets = new ArrayList<Bullet>(); t = new Thread(this); f = new JFrame("Shoot Em Up"); //Create new JFrame and set title f.setSize(600, 600); //setting frame 600px width and 600px height f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //close the program when close the frame f.setFocusable(true); //set frame focusable for listening key pressed f.add(this); //add this class to content pane of the frame ka = new KeyAdapter() { public void keyPressed(KeyEvent e) { switch(e.getKeyCode()) //listen key pressed and print relative value { case KeyEvent.VK_LEFT: position.x -= (position.x < 0 - P_SIZE/2) ? 0 : 10; break; case KeyEvent.VK_RIGHT: position.x += (position.x > getWidth() - P_SIZE/2) ? 0 : 10; break; case KeyEvent.VK_UP: position.y -= (position.y < 0 - P_SIZE/2) ? 0 : 10; break; case KeyEvent.VK_DOWN: position.y += (position.y > getHeight() - P_SIZE/2) ? 0 : 10; break; } afplayer.setToIdentity(); afplayer.translate(position.x, position.y); character = afplayer.createTransformedShape(player); repaint(); //redraw graphics component of the JPanel } }; setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); f.addKeyListener(ka); ma = new MouseAdapter() { public void mousePressed(MouseEvent e) { mouseCoords = e.getPoint(); //if (point == 0) //point += .1; } public void mouseReleased(MouseEvent e) { mouseCoords = null; } }; mma = new MouseMotionAdapter() { public void mouseMoved(MouseEvent e) { mouse = e.getPoint(); } public void mouseDragged(MouseEvent e) { mouse = e.getPoint(); } }; addMouseListener(ma); addMouseMotionListener(mma); f.setVisible(true); //set frame visibility t.start(); } public void run() { int count = 0; while(t != null) { repaint(); try {Thread.sleep(15); } catch(Exception e) {} if (mouseCoords != null && count % 10 == 0) { double xx0 = mouse.x - position.x; double yy0 = mouse.y - position.y; double m = yy0 / ((xx0 == 0) ? Integer.MAX_VALUE : xx0); double theta = Math.atan(m); theta = (mouse.x > position.x) ? theta : theta + Math.PI; bullets.add(new Bullet((Point)position.clone(), theta)); } if (count == Integer.MAX_VALUE) count = 0; count++; } } public void paint(Graphics _g) { Graphics2D g = (Graphics2D)_g; //cast Graphics in a Graphics2D object g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // if (point == 0) // { // g.setColor(new Color(0, 0, 0, .7f)); // g.fillRect(0, 0, getWidth(), getHeight()); // g.setColor(Color.LIGHT_GRAY); // g.setFont(new Font(("courier"), Font.PLAIN, 10)); // g.drawString(istruzioni, 20, 20); // } if (point < 100) { super.paintComponent(g); //reset component to the default painting settings g.setColor(Color.BLACK); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.WHITE); g.fill(character); //draw 'player' object into the JPanel if (bullets.size() > 0) for (int i=0; i<bullets.size(); i++) { bullets.get(i).update(g); for (int j=0; j<enemies.size(); j++) { if (enemies.get(j).shooted(bullets.get(i))) { point+=.2; } } } if (enemies.size() > 0) for (int i=0; i<enemies.size(); i++) { enemies.get(i).update(g); if (enemies.get(i).size < 20) enemies.set(i, new Enemy()); } if (mouse != null) { AffineTransform af = new AffineTransform(); double xx0 = mouse.x - position.x; double yy0 = mouse.y - position.y; double m = yy0 / ((xx0 == 0) ? Integer.MAX_VALUE : xx0); double theta = Math.atan(m); theta = (mouse.x > position.x) ? theta : theta + Math.PI; af.translate(position.x + P_SIZE/2, position.y + P_SIZE/2); af.rotate(theta); g.setColor(Color.WHITE); g.fill(af.createTransformedShape(new Rectangle2D.Double(0, -5, 35, 10))); } Font f = new Font("Arial", Font.ITALIC|Font.BOLD, 20); g.setColor(Color.RED); g.setFont(f); g.drawString("score: "+(int)(point), 20, 30); } else if (point >= 100) { t = null; removeMouseListener(ma); removeMouseMotionListener(mma); f.removeKeyListener(ka); g.setColor(new Color(0, 0, 0, .6f)); g.fillRect(0, 0, getWidth(), getHeight()); g.setPaint(new GradientPaint(getWidth()/2, 330, Color.BLACK, getWidth()/2, 250, Color.YELLOW)); g.setFont(new Font("Tahoma", Font.BOLD, 60)); g.drawString("End Of The Game", 40, 300); } } } |
Bullet.java
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 | import java.awt.*; import java.awt.geom.*; public class Bullet { protected Point position = null; private double theta = 0; protected Ellipse2D bullet = null; private AffineTransform afbullet = null; private Shape sbullet = null; public Bullet(Point initpos, double theta) { position = initpos; this.theta = theta; afbullet = new AffineTransform(); afbullet.translate(position.x + SEUFrame.P_SIZE, position.y + SEUFrame.P_SIZE/2); afbullet.rotate(theta); bullet = new Ellipse2D.Double(0, 0, 5, 5); sbullet = afbullet.createTransformedShape(bullet); } public void update(Graphics2D g) { bullet = new Ellipse2D.Double(bullet.getX()+5, -7/2, 20, 7); g.setColor(Color.WHITE); g.fill(updateShape()); } public Shape updateShape() { afbullet.setToIdentity(); afbullet.translate(position.x + SEUFrame.P_SIZE/2, position.y + SEUFrame.P_SIZE/2); afbullet.rotate(theta); return afbullet.createTransformedShape(bullet); } } |
Enemy.java
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 | import java.awt.*; import java.awt.geom.*; public class Enemy { protected Point2D position = null; protected double size = 50; private Rectangle2D enemy = null; private Color encol = null; public Enemy() { int vx = (Math.random() > Math.random()) ? 1 : -1; int vy = (Math.random() > Math.random()) ? 1 : -1; position = new Point2D.Double((Math.random()*100+100)*vx, (Math.random()*100+100)*vy); enemy = new Rectangle2D.Double(position.getX()-size/2+300, position.getY()-size/2+300, size, size); encol = new Color(rnd(), rnd(), rnd()); } public boolean shooted(Bullet b) { if (enemy.contains(b.updateShape().getBounds2D())) { size -= (size > 10) ? 10 : 0; return true; } return false; } public void update(Graphics2D g) { size += (size < 200) ? .25 : 0; enemy = new Rectangle2D.Double(position.getX()-size/2+300, position.getY()-size/2+300, size, size); g.setColor(encol); g.fill(enemy); g.setColor(Color.WHITE); g.draw(enemy); } public float rnd() { return (float)(Math.random()); } } |
