Shoot Em Up

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:

YouTube Preview Image

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());
	}
}

About the Author

Hello everyone! I'm Andrea Rastelli, a 23 year old programmer, designer, 3D animator and compositor. You may follow me on Twitter and Facebook