Home CPSC 220

More Graphics Programming

 

Keyboard Input

Keyboard input can be done for graphical components as well. To do this, we must create a class that implements the KeyListener interface.

This interface contains methods that are called when keys are pressed, released, or "typed". The KeyListener object must be added to a frame using the addKeyListener method. The "typed" event is used for textual input type controls (like text fields).

This example shows how this can be done:


import javax.swing.*;
import javax.imageio.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
import java.util.*;

// a character in a possible game
class Character {
    // the speed of the character measured in pixels/second
    private static final double SPEED = 100.0;
    
    // the image which is drawn for them
    private Image mario;
    
    // current position and speed
    private double x, y, dx, dy;

    public Character() {
        // load the image from a file
        try {
            mario = ImageIO.read(new File("mario.png"));
        } catch (Exception e) {
            mario = null;
        }

        // set default position and speed
        x = 100;
        y = 100;
        dx = 0;
        dy = 0;
    }

    public void draw(Graphics g) {
        // draw the image on the screen at current location
        g.drawImage(mario, (int) x, (int) y, null);
    }

    // stop the character from moving
    public void stop() {
        dx = 0;
        dy = 0;
    }

    // set speed to move in a specific direction
    public void left() {
        dx = -SPEED;
    }
    public void up() {
        dy = -SPEED;
    }
    public void right() {
        dx = SPEED;
    }
    public void down() {
        dy = SPEED;
    }

    // update based on the elapsed time
    public void update(double dt) {
        x += (dx * dt);
        y += (dy * dt);

        if (y < 0) {
            y = 500;
        }
        if (y > 500) {
            y = 0;
        }
        if (x < 0) {
            x = 500;
        }
        if (x > 500) {
            x = 0;
        }
    }
}

// the GameWorld is the GUI component we put in the window
class GameWorld extends JComponent implements KeyListener {
    // store the game object(s) and elapsed time
    private Character mario;
    private long elapsed;

    public GameWorld() {
        elapsed = new Date().getTime();
        mario = new Character();
    }
    
    @Override
    public void keyTyped(KeyEvent e) {
        // needed to implement the interface, but we don't care
        // about "typed" keys
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // this is what we care about - what key was pressed?
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            mario.right();
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            mario.left();
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            mario.up();
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            mario.down();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // if any key is released, stop the character
        mario.stop();
    }

    @Override
    public void paintComponent(Graphics g) {
        // draw the character
        mario.draw(g);

        // now update just like before
        long time_now = new Date().getTime();
        double seconds = (time_now - elapsed) / 1000.0f;
        elapsed = time_now;
        mario.update(seconds);

        // force an update of the screen
        revalidate();
        repaint();
    }
}

public class InputExample {
    public static void main(String args[]) {
        // create and set up the window.
        JFrame frame = new JFrame("Movement Example!");

        // make the program close when the window closes
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // add the GameWorld component
        GameWorld g = new GameWorld();
        frame.add(g);
        frame.addKeyListener(g);

        // display the window.
        frame.setSize(500, 500);
        frame.setVisible(true);
    }
}

The key pressed and released functions allow us to respond to different key types.


 

Mouse Input

We can also make graphical programs that respond to mouse input. This is done by implementing the "MouseListener" interface. This includes function for responding to different mouse events:

The program below moves the character to the position the mouse is clicked on:


import javax.swing.*;
import javax.imageio.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
import java.util.*;

// a character in a possible game
class Character {
    // the speed of the character measured in pixels/second
    private static final double SPEED = 100.0;
    
    // the image which is drawn for them
    private Image mario;
    
    // current position and speed
    private double x, y, dx, dy;

    public Character() {
        // load the image from a file
        try {
            mario = ImageIO.read(new File("mario.png"));
        } catch (Exception e) {
            mario = null;
        }

        // set default position and speed
        x = 100;
        y = 100;
        dx = 0;
        dy = 0;
    }
    
    public void setPosition(int newx, int newy) {
        x = newx;
        y = newy;
    }

    public void draw(Graphics g) {
        // draw the image on the screen at current location
        g.drawImage(mario, (int) x, (int) y, null);
    }

    // stop the character from moving
    public void stop() {
        dx = 0;
        dy = 0;
    }

    // set speed to move in a specific direction
    public void left() {
        dx = -SPEED;
    }
    public void up() {
        dy = -SPEED;
    }
    public void right() {
        dx = SPEED;
    }
    public void down() {
        dy = SPEED;
    }

    // update based on the elapsed time
    public void update(double dt) {
        x += (dx * dt);
        y += (dy * dt);

        if (y < 0) {
            y = 500;
        }
        if (y > 500) {
            y = 0;
        }
        if (x < 0) {
            x = 500;
        }
        if (x > 500) {
            x = 0;
        }
    }
}

// the GameWorld is the GUI component we put in the window
class GameWorld extends JComponent implements MouseListener {
    // store the game object(s) and elapsed time
    private Character mario;
    private long elapsed;

    public GameWorld() {
        elapsed = new Date().getTime();
        mario = new Character();
    }
    
    @Override
    public void paintComponent(Graphics g) {
        // draw the character
        mario.draw(g);

        // now update just like before
        long time_now = new Date().getTime();
        double seconds = (time_now - elapsed) / 1000.0f;
        elapsed = time_now;
        mario.update(seconds);

        // force an update of the screen
        revalidate();
        repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        // this is called when the mouse is clicked and then released
        
        // position Mario at this location
        mario.setPosition(e.getX(), e.getY());
        System.out.println("(" + e.getX() + ", " + e.getY() + ")");
    }

    @Override
    public void mousePressed(MouseEvent e) {
       // this is called when the mouse is first pressed
       // e.g. as the first part of a click
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // this is called when the mouse is released
        // e.g. as the second part of a click
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // this is called when the mouse enters the window
    }

    @Override
    public void mouseExited(MouseEvent e) {
       // this is called when the mouse leaves the window
    }
}

public class MouseInput {
    public static void main(String args[]) {
        // create and set up the window.
        JFrame frame = new JFrame("Movement Example!");

        // make the program close when the window closes
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // add the GameWorld component
        GameWorld g = new GameWorld();
        frame.add(g);
        
        // hookup the mouse listener to the game world
        g.addMouseListener(g);

        // display the window.
        frame.setSize(500, 500);
        frame.setVisible(true);
    }
}

We use the "getX" and "getY" methods to find the position of the mouse when the event occurs.


 

Animation

Mario doesn't look that good moving across the screen standing still. To make him look like he's running, we can create a simple animation by flipping between multiple images.

To make this work, we need to keep track of a few things:

Sometimes a single image is used, but we read from different portions of it. Images with several different images or frames in them are called sprite sheets.

This example shows a simple version of an animated sprite:


import javax.swing.*;
import javax.imageio.*;
import java.awt.*;
import java.io.*;
import java.awt.event.*;
import java.util.*;

// a character in a possible game
class Character {
    // the speed measured in pixels/second
    private static final double SPEED = 100.0;
    
    // the current position and speed
    private double x, y, dx, dy;

    // now we have an array of images! 
    private Image[] mario;
    
    // the current image we are on
    private int current = 0;
    
    // whether we are facing right or not
    private boolean right = true;
    
    // whether we are moving or not
    private boolean moving = false;

    // time between flips in the animation
    private static final double FLIP_TIME = 0.125;

    // time since last flip 
    private double timer = 0.0;

    public Character() {
        // load all the images 
        try {
            mario = new Image[4];
            mario[0] = ImageIO.read(new File("r1.png"));
            mario[1] = ImageIO.read(new File("r2.png"));
            mario[2] = ImageIO.read(new File("l1.png"));
            mario[3] = ImageIO.read(new File("l2.png"));
        } catch (Exception e) {
            mario = null;
        }

        // set default position and speed
        x = 100;
        y = 100;
        dx = 0;
        dy = 0;
    }

    // draw the character to the screen
    public void draw(Graphics g) {
        // add two to the index if going left
        int add = 0;
        if (!right) {
            add = 2;
        }

        // draw mario on the screen 
        g.drawImage(mario[current + add], (int) x, (int) y, null);
    }

    // stop mario 
    public void stop() {
        dx = 0;
        dy = 0;
        moving = false;
    }

    // left/up/right/down 
    public void left() {
        moving = true;
        right = false;
        dx = -SPEED;
    }
    public void up() {
        moving = true;
        dy = -SPEED;
    }
    public void right() {
        moving = true;
        right = true;
        dx = SPEED;
    }
    public void down() {
        moving = true;
        dy = SPEED;
    }

    // update him 
    public void update(double dt) {
        // update position first
        x += (dx * dt);
        y += (dy * dt);

        if (y < 0) {
            y = 500;
        }
        if (y > 500) {
            y = 0;
        }
        if (x < 0) {
            x = 500;
        }
        if (x > 500) {
            x = 0;
        }

        // then update the animation 
        if (moving) {
            timer += dt;
            
            // if it has been long enough since the last flip
            if (timer > FLIP_TIME) {
                // then flip the image
                timer = 0;
                current = (current + 1) % 2;
            }
        }
    }
}

class GameWorld extends JComponent implements KeyListener {
    // store the game object(s) and elapsed time
    private Character mario;
    private long elapsed;

    public GameWorld() {
        elapsed = new Date().getTime();
        mario = new Character();
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // needed to implement the interface, but we don't care
        // about "typed" keys
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // this is what we care about - what key was pressed?
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            mario.right();
        } else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            mario.left();
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            mario.up();
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            mario.down();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // if any key is released, stop the character
        mario.stop();
    }

    @Override
    public void paintComponent(Graphics g) {
        // draw the character
        mario.draw(g);

        // now update just like before
        long time_now = new Date().getTime();
        double seconds = (time_now - elapsed) / 1000.0f;
        elapsed = time_now;
        mario.update(seconds);

        // force an update of the screen
        revalidate();
        repaint();
    }
}

public class Animation {
    public static void main(String args[]) {
        // create and set up the window.
        JFrame frame = new JFrame("Animation Example!");

        // make the program close when the window closes
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // add the GameWorld component
        GameWorld g = new GameWorld();
        frame.add(g);
        frame.addKeyListener(g);

        // display the window.
        frame.setSize(500, 500);
        frame.setVisible(true);
    }
}

For this to run, you will need the four images:

This can be further improved by increasing the number of frames in the animation.

Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.