Moving sprites
last modified January 10, 2023
In this part of the Java 2D games tutorial we will work with sprites.
The term sprite has several meanings. It is used to denote an image or an animation in a scene.
It is also used to represent any movable object in a game. Also one of the meanings is the code that encapsulates a character in a game. In our tutorial by using sprite we refer to a movable object or its Java class.
Moving sprite
In the first example we have a spaceship. We can move the spaceship on the board using the cursor keys.
package com.zetcode; import java.awt.Image; import java.awt.event.KeyEvent; import javax.swing.ImageIcon; public class SpaceShip { private int dx; private int dy; private int x = 40; private int y = 60; private int w; private int h; private Image image; public SpaceShip() { loadImage(); } private void loadImage() { ImageIcon ii = new ImageIcon("src/resources/spaceship.png"); image = ii.getImage(); w = image.getWidth(null); h = image.getHeight(null); } public void move() { x += dx; y += dy; } public int getX() { return x; } public int getY() { return y; } public int getWidth() { return w; } public int getHeight() { return h; } public Image getImage() { return image; } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = -2; } if (key == KeyEvent.VK_RIGHT) { dx = 2; } if (key == KeyEvent.VK_UP) { dy = -2; } if (key == KeyEvent.VK_DOWN) { dy = 2; } } public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = 0; } if (key == KeyEvent.VK_RIGHT) { dx = 0; } if (key == KeyEvent.VK_UP) { dy = 0; } if (key == KeyEvent.VK_DOWN) { dy = 0; } } }
This class represents a spaceship. In this class we keep the image of
the sprite and the coordinates of the sprite. The keyPressed()
and keyReleased()
methods control whether the sprite is moving.
public void move() { x += dx; y += dy; }
The move()
method changes the coordinates of the sprite.
These x and y values are used in the paintComponent()
method to draw
the image of the sprite.
if (key == KeyEvent.VK_LEFT) { dx = 0; }
When we release the left cursor key, we set the dx
variable to zero.
The spacecraft will stop moving.
package com.zetcode; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JPanel; import javax.swing.Timer; public class Board extends JPanel implements ActionListener { private Timer timer; private SpaceShip spaceShip; private final int DELAY = 10; public Board() { initBoard(); } private void initBoard() { addKeyListener(new TAdapter()); setBackground(Color.black); setFocusable(true); spaceShip = new SpaceShip(); timer = new Timer(DELAY, this); timer.start(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); Toolkit.getDefaultToolkit().sync(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawImage(spaceShip.getImage(), spaceShip.getX(), spaceShip.getY(), this); } @Override public void actionPerformed(ActionEvent e) { step(); } private void step() { spaceShip.move(); repaint(spaceShip.getX()-1, spaceShip.getY()-1, spaceShip.getWidth()+2, spaceShip.getHeight()+2); } private class TAdapter extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { spaceShip.keyReleased(e); } @Override public void keyPressed(KeyEvent e) { spaceShip.keyPressed(e); } } }
This is the Board
class.
private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawImage(ship.getImage(), ship.getX(), ship.getY(), this); }
In the doDrawing()
method, we draw the spaceship with the
drawImage()
method. We get the image and the coordinates
from the sprite class.
@Override public void actionPerformed(ActionEvent e) { step(); }
The actionPerformed()
method is called every DELAY
ms. We call the step()
method.
private void step() { ship.move(); repaint(ship.getX()-1, ship.getY()-1, ship.getWidth()+2, ship.getHeight()+2); }
We move the sprite and repaint the part of the board that has changed. We use a small optimisation technique that repaints only the small area of the window that actually changed.
private class TAdapter extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { craft.keyReleased(e); } @Override public void keyPressed(KeyEvent e) { craft.keyPressed(e); } }
In the Board
class we listen for key events. The overridden
methods of the KeyAdapter
class delegate the processing to
the methods of the Craft
class.
package com.zetcode; import java.awt.EventQueue; import javax.swing.JFrame; public class MovingSpriteEx extends JFrame { public MovingSpriteEx() { initUI(); } private void initUI() { add(new Board()); setTitle("Moving sprite"); setSize(400, 300); setLocationRelativeTo(null); setResizable(false); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { MovingSpriteEx ex = new MovingSpriteEx(); ex.setVisible(true); }); } }
This is the main class.
Shooting missiles
In the next example we add another sprite type to our example—a missile. The missiles are launched with the Space key.
package com.zetcode; import java.awt.Image; import javax.swing.ImageIcon; public class Sprite { protected int x; protected int y; protected int width; protected int height; protected boolean visible; protected Image image; public Sprite(int x, int y) { this.x = x; this.y = y; visible = true; } protected void loadImage(String imageName) { ImageIcon ii = new ImageIcon(imageName); image = ii.getImage(); } protected void getImageDimensions() { width = image.getWidth(null); height = image.getHeight(null); } public Image getImage() { return image; } public int getX() { return x; } public int getY() { return y; } public boolean isVisible() { return visible; } public void setVisible(Boolean visible) { this.visible = visible; } }
The Sprite
class shares common code from the Missile
and SpaceShip
classes.
public Sprite(int x, int y) { this.x = x; this.y = y; visible = true; }
The constructor initiates the x and y coordinates and the
visible
variable.
package com.zetcode; public class Missile extends Sprite { private final int BOARD_WIDTH = 390; private final int MISSILE_SPEED = 2; public Missile(int x, int y) { super(x, y); initMissile(); } private void initMissile() { loadImage("src/resources/missile.png"); getImageDimensions(); } public void move() { x += MISSILE_SPEED; if (x > BOARD_WIDTH) { visible = false; } } }
Here we have a new sprite called Missile
.
public void move() { x += MISSILE_SPEED; if (x > BOARD_WIDTH) { vis = false; } }
The missile moves at constant speed. When it hits the right border of the
Board
, it becomes invisible. It is then removed from the list of missiles.
package com.zetcode; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; public class SpaceShip extends Sprite { private int dx; private int dy; private List<Missile> missiles; public SpaceShip(int x, int y) { super(x, y); initSpaceShip(); } private void initSpaceShip() { missiles = new ArrayList<>(); loadImage("src/resources/spaceship.png"); getImageDimensions(); } public void move() { x += dx; y += dy; } public List<Missile> getMissiles() { return missiles; } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_SPACE) { fire(); } if (key == KeyEvent.VK_LEFT) { dx = -1; } if (key == KeyEvent.VK_RIGHT) { dx = 1; } if (key == KeyEvent.VK_UP) { dy = -1; } if (key == KeyEvent.VK_DOWN) { dy = 1; } } public void fire() { missiles.add(new Missile(x + width, y + height / 2)); } public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = 0; } if (key == KeyEvent.VK_RIGHT) { dx = 0; } if (key == KeyEvent.VK_UP) { dy = 0; } if (key == KeyEvent.VK_DOWN) { dy = 0; } } }
This is the SpaceShip
class.
if (key == KeyEvent.VK_SPACE) { fire(); }
If we press the Space key, we fire.
public void fire() { missiles.add(new Missile(x + width, y + height / 2)); }
The fire()
method creates a new Missile
object
and adds it to the list of missiles.
public List<Missile> getMissiles() { return missiles; }
The getMissiles()
method returns the list of missiles. It
is called from the Board
class.
package com.zetcode; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.List; import javax.swing.JPanel; import javax.swing.Timer; public class Board extends JPanel implements ActionListener { private final int ICRAFT_X = 40; private final int ICRAFT_Y = 60; private final int DELAY = 10; private Timer timer; private SpaceShip spaceShip; public Board() { initBoard(); } private void initBoard() { addKeyListener(new TAdapter()); setBackground(Color.BLACK); setFocusable(true); spaceShip = new SpaceShip(ICRAFT_X, ICRAFT_Y); timer = new Timer(DELAY, this); timer.start(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); Toolkit.getDefaultToolkit().sync(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawImage(spaceShip.getImage(), spaceShip.getX(), spaceShip.getY(), this); List<Missile> missiles = spaceShip.getMissiles(); for (Missile missile : missiles) { g2d.drawImage(missile.getImage(), missile.getX(), missile.getY(), this); } } @Override public void actionPerformed(ActionEvent e) { updateMissiles(); updateSpaceShip(); repaint(); } private void updateMissiles() { List<Missile> missiles = spaceShip.getMissiles(); for (int i = 0; i < missiles.size(); i++) { Missile missile = missiles.get(i); if (missile.isVisible()) { missile.move(); } else { missiles.remove(i); } } } private void updateSpaceShip() { spaceShip.move(); } private class TAdapter extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { spaceShip.keyReleased(e); } @Override public void keyPressed(KeyEvent e) { spaceShip.keyPressed(e); } } }
This is the Board
class.
private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.drawImage(spaceShip.getImage(), spaceShip.getX(), spaceShip.getY(), this); List<Missile> missiles = spaceShip.getMissiles(); for (Missile missile : missiles) { g2d.drawImage(missile.getImage(), missile.getX(), missile.getY(), this); } }
In the doDrawing()
method, we draw the craft and all the
available missiles.
private void updateMissiles() { List<Missile> missiles = spaceShip.getMissiles(); for (int i = 0; i < missiles.size(); i++) { Missile missile = missiles.get(i); if (missile.isVisible()) { missile.move(); } else { missiles.remove(i); } } }
In the updateMissiles()
method we parse all missiles
from the missiles
list. Depending on what the
isVisible()
method returns, we either move the missile or
remove it from the container.
package com.zetcode; import java.awt.EventQueue; import javax.swing.JFrame; public class ShootingMissilesEx extends JFrame { public ShootingMissilesEx() { initUI(); } private void initUI() { add(new Board()); setSize(400, 300); setResizable(false); setTitle("Shooting missiles"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { ShootingMissilesEx ex = new ShootingMissilesEx(); ex.setVisible(true); }); } }
Finally, this is the main class.
In this chapter, we have covered sprites.