├── spaceinvaders.png ├── src ├── images │ ├── bomb.png │ ├── shot.png │ ├── alien.png │ ├── player.png │ └── explosion.png └── com │ └── zetcode │ ├── Commons.java │ ├── sprite │ ├── Shot.java │ ├── Sprite.java │ ├── Alien.java │ └── Player.java │ ├── SpaceInvaders.java │ └── Board.java ├── README.md └── LICENSE /spaceinvaders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/spaceinvaders.png -------------------------------------------------------------------------------- /src/images/bomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/src/images/bomb.png -------------------------------------------------------------------------------- /src/images/shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/src/images/shot.png -------------------------------------------------------------------------------- /src/images/alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/src/images/alien.png -------------------------------------------------------------------------------- /src/images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/src/images/player.png -------------------------------------------------------------------------------- /src/images/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janbodnar/Java-Space-Invaders/HEAD/src/images/explosion.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Space Invaders 2 | Java Space Invaders game clone source code 3 | 4 | https://zetcode.com/javagames/spaceinvaders/ 5 | 6 | ![Space Invaders screenshot](spaceinvaders.png) 7 | 8 | -------------------------------------------------------------------------------- /src/com/zetcode/Commons.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | public interface Commons { 4 | 5 | int BOARD_WIDTH = 358; 6 | int BOARD_HEIGHT = 350; 7 | int BORDER_RIGHT = 30; 8 | int BORDER_LEFT = 5; 9 | 10 | int GROUND = 290; 11 | int BOMB_HEIGHT = 5; 12 | 13 | int ALIEN_HEIGHT = 12; 14 | int ALIEN_WIDTH = 12; 15 | int ALIEN_INIT_X = 150; 16 | int ALIEN_INIT_Y = 5; 17 | 18 | int GO_DOWN = 15; 19 | int NUMBER_OF_ALIENS_TO_DESTROY = 24; 20 | int CHANCE = 5; 21 | int DELAY = 17; 22 | int PLAYER_WIDTH = 15; 23 | int PLAYER_HEIGHT = 10; 24 | } 25 | -------------------------------------------------------------------------------- /src/com/zetcode/sprite/Shot.java: -------------------------------------------------------------------------------- 1 | package com.zetcode.sprite; 2 | 3 | import javax.swing.ImageIcon; 4 | 5 | public class Shot extends Sprite { 6 | 7 | public Shot() { 8 | } 9 | 10 | public Shot(int x, int y) { 11 | 12 | initShot(x, y); 13 | } 14 | 15 | private void initShot(int x, int y) { 16 | 17 | var shotImg = "src/images/shot.png"; 18 | var ii = new ImageIcon(shotImg); 19 | setImage(ii.getImage()); 20 | 21 | int H_SPACE = 6; 22 | setX(x + H_SPACE); 23 | 24 | int V_SPACE = 1; 25 | setY(y - V_SPACE); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/zetcode/SpaceInvaders.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import java.awt.EventQueue; 4 | import javax.swing.JFrame; 5 | 6 | public class SpaceInvaders extends JFrame { 7 | 8 | public SpaceInvaders() { 9 | 10 | initUI(); 11 | } 12 | 13 | private void initUI() { 14 | 15 | add(new Board()); 16 | 17 | setTitle("Space Invaders"); 18 | setSize(Commons.BOARD_WIDTH, Commons.BOARD_HEIGHT); 19 | 20 | setDefaultCloseOperation(EXIT_ON_CLOSE); 21 | setResizable(false); 22 | setLocationRelativeTo(null); 23 | } 24 | 25 | public static void main(String[] args) { 26 | 27 | EventQueue.invokeLater(() -> { 28 | 29 | var ex = new SpaceInvaders(); 30 | ex.setVisible(true); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/zetcode/sprite/Sprite.java: -------------------------------------------------------------------------------- 1 | package com.zetcode.sprite; 2 | 3 | import java.awt.Image; 4 | 5 | public class Sprite { 6 | 7 | private boolean visible; 8 | private Image image; 9 | private boolean dying; 10 | 11 | int x; 12 | int y; 13 | int dx; 14 | 15 | public Sprite() { 16 | 17 | visible = true; 18 | } 19 | 20 | public void die() { 21 | 22 | visible = false; 23 | } 24 | 25 | public boolean isVisible() { 26 | 27 | return visible; 28 | } 29 | 30 | protected void setVisible(boolean visible) { 31 | 32 | this.visible = visible; 33 | } 34 | 35 | public void setImage(Image image) { 36 | 37 | this.image = image; 38 | } 39 | 40 | public Image getImage() { 41 | 42 | return image; 43 | } 44 | 45 | public void setX(int x) { 46 | 47 | this.x = x; 48 | } 49 | 50 | public void setY(int y) { 51 | 52 | this.y = y; 53 | } 54 | 55 | public int getY() { 56 | 57 | return y; 58 | } 59 | 60 | public int getX() { 61 | 62 | return x; 63 | } 64 | 65 | public void setDying(boolean dying) { 66 | 67 | this.dying = dying; 68 | } 69 | 70 | public boolean isDying() { 71 | 72 | return this.dying; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Jan Bodnar 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/com/zetcode/sprite/Alien.java: -------------------------------------------------------------------------------- 1 | package com.zetcode.sprite; 2 | 3 | import javax.swing.ImageIcon; 4 | 5 | public class Alien extends Sprite { 6 | 7 | private Bomb bomb; 8 | 9 | public Alien(int x, int y) { 10 | 11 | initAlien(x, y); 12 | } 13 | 14 | private void initAlien(int x, int y) { 15 | 16 | this.x = x; 17 | this.y = y; 18 | 19 | bomb = new Bomb(x, y); 20 | 21 | var alienImg = "src/images/alien.png"; 22 | var ii = new ImageIcon(alienImg); 23 | 24 | setImage(ii.getImage()); 25 | } 26 | 27 | public void act(int direction) { 28 | 29 | this.x += direction; 30 | } 31 | 32 | public Bomb getBomb() { 33 | 34 | return bomb; 35 | } 36 | 37 | public class Bomb extends Sprite { 38 | 39 | private boolean destroyed; 40 | 41 | public Bomb(int x, int y) { 42 | 43 | initBomb(x, y); 44 | } 45 | 46 | private void initBomb(int x, int y) { 47 | 48 | setDestroyed(true); 49 | 50 | this.x = x; 51 | this.y = y; 52 | 53 | var bombImg = "src/images/bomb.png"; 54 | var ii = new ImageIcon(bombImg); 55 | setImage(ii.getImage()); 56 | } 57 | 58 | public void setDestroyed(boolean destroyed) { 59 | 60 | this.destroyed = destroyed; 61 | } 62 | 63 | public boolean isDestroyed() { 64 | 65 | return destroyed; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/zetcode/sprite/Player.java: -------------------------------------------------------------------------------- 1 | package com.zetcode.sprite; 2 | 3 | import com.zetcode.Commons; 4 | 5 | import javax.swing.ImageIcon; 6 | import java.awt.event.KeyEvent; 7 | 8 | public class Player extends Sprite { 9 | 10 | private int width; 11 | 12 | public Player() { 13 | 14 | initPlayer(); 15 | } 16 | 17 | private void initPlayer() { 18 | 19 | var playerImg = "src/images/player.png"; 20 | var ii = new ImageIcon(playerImg); 21 | 22 | width = ii.getImage().getWidth(null); 23 | setImage(ii.getImage()); 24 | 25 | int START_X = 270; 26 | setX(START_X); 27 | 28 | int START_Y = 280; 29 | setY(START_Y); 30 | } 31 | 32 | public void act() { 33 | 34 | x += dx; 35 | 36 | if (x <= 2) { 37 | 38 | x = 2; 39 | } 40 | 41 | if (x >= Commons.BOARD_WIDTH - 2 * width) { 42 | 43 | x = Commons.BOARD_WIDTH - 2 * width; 44 | } 45 | } 46 | 47 | public void keyPressed(KeyEvent e) { 48 | 49 | int key = e.getKeyCode(); 50 | 51 | if (key == KeyEvent.VK_LEFT) { 52 | 53 | dx = -2; 54 | } 55 | 56 | if (key == KeyEvent.VK_RIGHT) { 57 | 58 | dx = 2; 59 | } 60 | } 61 | 62 | public void keyReleased(KeyEvent e) { 63 | 64 | int key = e.getKeyCode(); 65 | 66 | if (key == KeyEvent.VK_LEFT) { 67 | 68 | dx = 0; 69 | } 70 | 71 | if (key == KeyEvent.VK_RIGHT) { 72 | 73 | dx = 0; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/com/zetcode/Board.java: -------------------------------------------------------------------------------- 1 | package com.zetcode; 2 | 3 | import com.zetcode.sprite.Alien; 4 | import com.zetcode.sprite.Player; 5 | import com.zetcode.sprite.Shot; 6 | 7 | import javax.swing.ImageIcon; 8 | import javax.swing.JPanel; 9 | import javax.swing.Timer; 10 | import java.awt.Color; 11 | import java.awt.Dimension; 12 | import java.awt.Font; 13 | import java.awt.Graphics; 14 | import java.awt.Toolkit; 15 | import java.awt.event.ActionEvent; 16 | import java.awt.event.ActionListener; 17 | import java.awt.event.KeyAdapter; 18 | import java.awt.event.KeyEvent; 19 | import java.util.ArrayList; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.Random; 23 | 24 | public class Board extends JPanel { 25 | 26 | private Dimension d; 27 | private List aliens; 28 | private Player player; 29 | private Shot shot; 30 | 31 | private int direction = -1; 32 | private int deaths = 0; 33 | 34 | private boolean inGame = true; 35 | private String explImg = "src/images/explosion.png"; 36 | private String message = "Game Over"; 37 | 38 | private Timer timer; 39 | 40 | 41 | public Board() { 42 | 43 | initBoard(); 44 | gameInit(); 45 | } 46 | 47 | private void initBoard() { 48 | 49 | addKeyListener(new TAdapter()); 50 | setFocusable(true); 51 | d = new Dimension(Commons.BOARD_WIDTH, Commons.BOARD_HEIGHT); 52 | setBackground(Color.black); 53 | 54 | timer = new Timer(Commons.DELAY, new GameCycle()); 55 | timer.start(); 56 | 57 | gameInit(); 58 | } 59 | 60 | 61 | private void gameInit() { 62 | 63 | aliens = new ArrayList<>(); 64 | 65 | for (int i = 0; i < 4; i++) { 66 | for (int j = 0; j < 6; j++) { 67 | 68 | var alien = new Alien(Commons.ALIEN_INIT_X + 18 * j, 69 | Commons.ALIEN_INIT_Y + 18 * i); 70 | aliens.add(alien); 71 | } 72 | } 73 | 74 | player = new Player(); 75 | shot = new Shot(); 76 | } 77 | 78 | private void drawAliens(Graphics g) { 79 | 80 | for (Alien alien : aliens) { 81 | 82 | if (alien.isVisible()) { 83 | 84 | g.drawImage(alien.getImage(), alien.getX(), alien.getY(), this); 85 | } 86 | 87 | if (alien.isDying()) { 88 | 89 | alien.die(); 90 | } 91 | } 92 | } 93 | 94 | private void drawPlayer(Graphics g) { 95 | 96 | if (player.isVisible()) { 97 | 98 | g.drawImage(player.getImage(), player.getX(), player.getY(), this); 99 | } 100 | 101 | if (player.isDying()) { 102 | 103 | player.die(); 104 | inGame = false; 105 | } 106 | } 107 | 108 | private void drawShot(Graphics g) { 109 | 110 | if (shot.isVisible()) { 111 | 112 | g.drawImage(shot.getImage(), shot.getX(), shot.getY(), this); 113 | } 114 | } 115 | 116 | private void drawBombing(Graphics g) { 117 | 118 | for (Alien a : aliens) { 119 | 120 | Alien.Bomb b = a.getBomb(); 121 | 122 | if (!b.isDestroyed()) { 123 | 124 | g.drawImage(b.getImage(), b.getX(), b.getY(), this); 125 | } 126 | } 127 | } 128 | 129 | @Override 130 | public void paintComponent(Graphics g) { 131 | super.paintComponent(g); 132 | 133 | doDrawing(g); 134 | } 135 | 136 | private void doDrawing(Graphics g) { 137 | 138 | g.setColor(Color.black); 139 | g.fillRect(0, 0, d.width, d.height); 140 | g.setColor(Color.green); 141 | 142 | if (inGame) { 143 | 144 | g.drawLine(0, Commons.GROUND, 145 | Commons.BOARD_WIDTH, Commons.GROUND); 146 | 147 | drawAliens(g); 148 | drawPlayer(g); 149 | drawShot(g); 150 | drawBombing(g); 151 | 152 | } else { 153 | 154 | if (timer.isRunning()) { 155 | timer.stop(); 156 | } 157 | 158 | gameOver(g); 159 | } 160 | 161 | Toolkit.getDefaultToolkit().sync(); 162 | } 163 | 164 | private void gameOver(Graphics g) { 165 | 166 | g.setColor(Color.black); 167 | g.fillRect(0, 0, Commons.BOARD_WIDTH, Commons.BOARD_HEIGHT); 168 | 169 | g.setColor(new Color(0, 32, 48)); 170 | g.fillRect(50, Commons.BOARD_WIDTH / 2 - 30, Commons.BOARD_WIDTH - 100, 50); 171 | g.setColor(Color.white); 172 | g.drawRect(50, Commons.BOARD_WIDTH / 2 - 30, Commons.BOARD_WIDTH - 100, 50); 173 | 174 | var small = new Font("Helvetica", Font.BOLD, 14); 175 | var fontMetrics = this.getFontMetrics(small); 176 | 177 | g.setColor(Color.white); 178 | g.setFont(small); 179 | g.drawString(message, (Commons.BOARD_WIDTH - fontMetrics.stringWidth(message)) / 2, 180 | Commons.BOARD_WIDTH / 2); 181 | } 182 | 183 | private void update() { 184 | 185 | if (deaths == Commons.NUMBER_OF_ALIENS_TO_DESTROY) { 186 | 187 | inGame = false; 188 | timer.stop(); 189 | message = "Game won!"; 190 | } 191 | 192 | // player 193 | player.act(); 194 | 195 | // shot 196 | if (shot.isVisible()) { 197 | 198 | int shotX = shot.getX(); 199 | int shotY = shot.getY(); 200 | 201 | for (Alien alien : aliens) { 202 | 203 | int alienX = alien.getX(); 204 | int alienY = alien.getY(); 205 | 206 | if (alien.isVisible() && shot.isVisible()) { 207 | if (shotX >= (alienX) 208 | && shotX <= (alienX + Commons.ALIEN_WIDTH) 209 | && shotY >= (alienY) 210 | && shotY <= (alienY + Commons.ALIEN_HEIGHT)) { 211 | 212 | var ii = new ImageIcon(explImg); 213 | alien.setImage(ii.getImage()); 214 | alien.setDying(true); 215 | deaths++; 216 | shot.die(); 217 | } 218 | } 219 | } 220 | 221 | int y = shot.getY(); 222 | y -= 4; 223 | 224 | if (y < 0) { 225 | shot.die(); 226 | } else { 227 | shot.setY(y); 228 | } 229 | } 230 | 231 | // aliens 232 | 233 | for (Alien alien : aliens) { 234 | 235 | int x = alien.getX(); 236 | 237 | if (x >= Commons.BOARD_WIDTH - Commons.BORDER_RIGHT && direction != -1) { 238 | 239 | direction = -1; 240 | 241 | Iterator i1 = aliens.iterator(); 242 | 243 | while (i1.hasNext()) { 244 | 245 | Alien a2 = i1.next(); 246 | a2.setY(a2.getY() + Commons.GO_DOWN); 247 | } 248 | } 249 | 250 | if (x <= Commons.BORDER_LEFT && direction != 1) { 251 | 252 | direction = 1; 253 | 254 | Iterator i2 = aliens.iterator(); 255 | 256 | while (i2.hasNext()) { 257 | 258 | Alien a = i2.next(); 259 | a.setY(a.getY() + Commons.GO_DOWN); 260 | } 261 | } 262 | } 263 | 264 | Iterator it = aliens.iterator(); 265 | 266 | while (it.hasNext()) { 267 | 268 | Alien alien = it.next(); 269 | 270 | if (alien.isVisible()) { 271 | 272 | int y = alien.getY(); 273 | 274 | if (y > Commons.GROUND - Commons.ALIEN_HEIGHT) { 275 | inGame = false; 276 | message = "Invasion!"; 277 | } 278 | 279 | alien.act(direction); 280 | } 281 | } 282 | 283 | // bombs 284 | var generator = new Random(); 285 | 286 | for (Alien alien : aliens) { 287 | 288 | int shot = generator.nextInt(15); 289 | Alien.Bomb bomb = alien.getBomb(); 290 | 291 | if (shot == Commons.CHANCE && alien.isVisible() && bomb.isDestroyed()) { 292 | 293 | bomb.setDestroyed(false); 294 | bomb.setX(alien.getX()); 295 | bomb.setY(alien.getY()); 296 | } 297 | 298 | int bombX = bomb.getX(); 299 | int bombY = bomb.getY(); 300 | int playerX = player.getX(); 301 | int playerY = player.getY(); 302 | 303 | if (player.isVisible() && !bomb.isDestroyed()) { 304 | 305 | if (bombX >= (playerX) 306 | && bombX <= (playerX + Commons.PLAYER_WIDTH) 307 | && bombY >= (playerY) 308 | && bombY <= (playerY + Commons.PLAYER_HEIGHT)) { 309 | 310 | var ii = new ImageIcon(explImg); 311 | player.setImage(ii.getImage()); 312 | player.setDying(true); 313 | bomb.setDestroyed(true); 314 | } 315 | } 316 | 317 | if (!bomb.isDestroyed()) { 318 | 319 | bomb.setY(bomb.getY() + 1); 320 | 321 | if (bomb.getY() >= Commons.GROUND - Commons.BOMB_HEIGHT) { 322 | 323 | bomb.setDestroyed(true); 324 | } 325 | } 326 | } 327 | } 328 | 329 | private void doGameCycle() { 330 | 331 | update(); 332 | repaint(); 333 | } 334 | 335 | private class GameCycle implements ActionListener { 336 | 337 | @Override 338 | public void actionPerformed(ActionEvent e) { 339 | 340 | doGameCycle(); 341 | } 342 | } 343 | 344 | private class TAdapter extends KeyAdapter { 345 | 346 | @Override 347 | public void keyReleased(KeyEvent e) { 348 | 349 | player.keyReleased(e); 350 | } 351 | 352 | @Override 353 | public void keyPressed(KeyEvent e) { 354 | 355 | player.keyPressed(e); 356 | 357 | int x = player.getX(); 358 | int y = player.getY(); 359 | 360 | int key = e.getKeyCode(); 361 | 362 | if (key == KeyEvent.VK_SPACE) { 363 | 364 | if (inGame) { 365 | 366 | if (!shot.isVisible()) { 367 | 368 | shot = new Shot(x, y); 369 | } 370 | } 371 | } 372 | } 373 | } 374 | } 375 | --------------------------------------------------------------------------------