User Tools

Site Tools


cs498gd:java_images_animation_sprites

Java: Images, Animation, Sprites


Image buffering

drawImage() and Image Quirks

  • drawImage() can be weird, in that it does not necessarily draw an Image immediately.
  • Loading an image to memory (RAM) and manipulating it there (“buffering” the image) makes this behavior of drawImage() less likely to occur.
    • Therefore, we want to use buffering when doing animation and gaming.

BufferedImage and Image Processing

  • BufferedImage class - Provides methods for storing, interpreting, and rendering image pixel data
  • Effective for large “unchanging” (static) images that take a long time to create: Create them once, then display them repeatedly from memory, instead of recomputing them each time.
  • Example of loading a buffered image:
        // Load an image using BufferedImage, with optimization
        public BufferedImage loadImageBuffered (String imageName)
        {

          // Some preliminaries and setup
          GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
          GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
        
          try {
            BufferedImage bi = ImageIO.read(getClass().getResource(imageName));
                // imageName read into bi
            int transparency = bi.getColorModel().getTransparency();
            BufferedImage copy = gc.createCompatibleImage(bi.getWidth(), bi.getHeight(), transparency);
                // buffered, optimized copy of bi made
            Graphics2D g2d = copy.createGraphics();
            g2d.drawImage(bi, 0, 0, null);
                // bi drawn
            g2d.dispose();
            return copy;
          }
          catch (IOException e) {
            System.err.println("Image loading error: " + e.toString());
            return null;
          }
        }
  • Buffering enables you to manipulate and display pixel-mapped images whose data is stored in memory.
    • BufferedImage (input) ⇒ Buffered image operation ⇒ BufferedImage (output)
    • Possible Graphics2D transformations on Images:
      • rotate()
      • scale()
      • shear()
    • Possible applications: sprites, side scrolling games, animations

Animation and Threads

The Game Loop

  • The key to a game's operation
  • Responsible for keeping track of the current frame and for requesting periodic screen updates
  • The pseudocode / idea:
      while (game is running) {

        while (isPaused && isRunning) {
          sleep(someInterval);
        }

        updateState();
        processInputs();
        updateAI();
        updatePhysicsAndCollisions();
        updateAnimations();
        playSounds();
        updateVideo();
        drawGraphics();
        ...
        ...
        ...
      }
  • Inputs or none, the game loop must run.
  • Some of the above has to run concurrently or performance will suffer.

Threads

  • Animation creates some kind of motion on the screen by drawing successive frames at a relatively high speed.
  • Threads - allow for multiple tasks to run concurrently in a program
  • Think multitasking in an operating system (i.e., run more than one program at a time)
  • Implement a thread containing the animation loop.
    • MUST NOT implement animation loop in paintComponent()
    • In a threaded panel (or threaded canvas (AWT))

Animation Framework - Simple

      import java.awt.*;
      import javax.swing.*;

      // "Runnable" JPanel is a thread
      public class AnimationFramework extends JPanel implements Runnable
      {
        // http://www.javablogging.com/what-is-serialversionuid/
        // Certain IDEs will warn that you are missing SerialVersionUID
        private static final long serialVersionUID = 1L;

        private Thread animator;

        public AnimationFramework()
        {
        }

        // start(), stop(), run() are Thread methods
        public void start()
        {
          animator = new Thread(this);
          animator.start();
        }

        public void stop()
        {
          animator = null;
        }

        public void run()
        {
          long start = System.currentTimeMillis();
          Thread current = Thread.currentThread();

          while (current == animator) {
            try {
              Thread.sleep(0);
            }
            catch (InterruptedException e) {
              System.err.println("An error occurred: " + e.toString());
              e.printStackTrace();
            }

            // Draw something
            repaint();

            // Figure out how fast it is running
            if (start + 1000 < System.currentTimeMillis()) {
              start = System.currentTimeMillis();
            }
          }
        }
      		   
        public void paintComponent (Graphics g)
        {
          // Do your drawing stuff here
        }
      }
  • In your frame class, you would instantiate this threaded panel, and then start the animation by executing the panel's start() method.
  • Problems with the above framework:
    • The framework runs at breakneck speed, as fast as possible, as evidenced by “Thread.sleep(0)”.
      • The call to repaint() happens with no pause.
        • Maybe fine for animation, but for games, usually not a good thing.
    • As an alternative, the animation could sleep for a fixed amount of time between frames (repaints).
      • Again, fine for animation, but in games, you could end up waiting too long between frames.

Handling Frames Per Second (FPS)

  • A frame is one pass through the update-render-sleep loop inside the JPanel thread's run() method.
    • … in which the repaint() occurs.
  • Theoretically, the loop's execution speed should be about the same on all platforms, except …
    • On a slower machine ⇒ too slow ⇒ unplayable game
    • On a faster machine ⇒ too fast ⇒ unplayable game
  • Need to compute the estimated required delay between frames based on the current time.
    • Keep track of the starting time.
  • There are many ways to optimize the FPS and thread sleeping times.
    • The goal is to reduce “lag” as much as possible.

An Improved Animation Framework

      import java.awt.*;
      import javax.swing.*;

      public class AnimationFramework extends JPanel implements Runnable
      {
        private static final long serialVersionUID = 1L;

        private Thread animator;

        // Going to adjust frame delay
        private int fps, delay;

        public AnimationFramework()
        {
          // 30 frames per second; considered sort of a minimum acceptable frame rate
          fps = 30;

          // 1000 milliseconds = 1 second
          delay = 1000 / fps;
          
          // for 30 fps, delay will be about 33.3 ms.
        }

        public AnimationFramework (int fps)
        {
          this.fps = fps;
          delay = 1000 / this.fps;
        }

        public void start()
        {
          animator = new Thread(this);
          animator.start();
        }

        public void stop()
        {
          animator = null;
        }

        public void run()
        {
          long start = System.currentTimeMillis();
          Thread current = Thread.currentThread();

          while (current == animator) {
            try {
              start += delay;
              // Delay next frame depending on how far behind current time we are.
              // For simple animations, incrementing start by the delay value
              // will probably keep pace with the current time, but if we're doing
              // a lot in this while loop, we'll eventually fall behind current time.
              // So to keep up, sleep(0) will be used. Also, see the if statement below.
              Thread.sleep(Math.max(0, start - System.currentTimeMillis()));
            }
            catch (InterruptedException e) {
              System.err.println("An error occurred: " + e.toString());
              e.printStackTrace();
            }

            // Draw something
            repaint();

            // Figure out how fast it is running;
            // if start is more than 1000 ms behind current time, reset start
            // to current time
            if (start + 1000 < System.currentTimeMillis()) {
              start = System.currentTimeMillis();
            }
          }
        }
      		   
        public void paintComponent (Graphics g)
        {
          // Do your drawing stuff in this routine
        }
      }

A Little More on Timing

  • Unlike many other applications, timing is critical for games:
    • Updating the state of the game by the duration of the previous frame
    • Locking the frame rate of a renderer
    • Maintaining and evaluating proper transition of AI states
    • Correctly identifying times to change frames in an animation (as we have done in the above)
    • Testing for the proper amount of time to pass before getting additional input from a device
  • To get the system-level timing in Java: long currentTime = System.currentTimeMillis()
    • For our purposes, this is acceptable.
  • With Java 1.5, a very high-resolution timer was introduced: long currentTime = System.nanoTime()
    • nanoseconds

Java2D code samples


Sprites

  • Sprite - Any object that appears on the screen
  • Overlayed on background surface
  • Can be an image or drawing
    • Can be motionless (dull)
    • Can be animated: move around the screen randomly
    • Even better: can respond to the environment
  • Hopefully, your sprite images have transparent pixels
  • We have already laid most of the groundwork for creating sprite.
    • Keep track of the x and y coordinates
    • Images should be BufferedImage
    • Draw the sprite
    • Update the sprite (e.g., coordinates, speed)
    • Keep track of it's state (e.g., hidden/shown)

GameSprite class framework examples

a) Multiple moving circles and squares using a GameSprite class:

b) A snowfall using a GameSprite class:

  • Notable features:
    • Scaling and rotation of an external snowflake image
    • A background image
    • Keyboard events to blow snowflakes horizontally
    • screen edge warping (disappear out one side, come back from the other side)

Animated Sprites

  • What if we have a strip image?

(bat.gif)

(explosion.gif)

  • What if we have a series of images?
  • The idea: flipbook
  • Updating and extending GameSprite
  • The key:
  public ArrayList <BufferedImage> loadStripImageArray(String imageName, int numImages)
  { 

    ArrayList<BufferedImage> list = new ArrayList<BufferedImage>();
    GraphicsEnvironment ge        = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsConfiguration gc      = ge.getDefaultScreenDevice().getDefaultConfiguration();
    BufferedImage stripIm;

    if ( numImages > 0 ) {
      stripIm = loadImageBuffered( imageName );

      if (stripIm != null) {
        int width = (int) stripIm.getWidth() / numImages;
        int height = stripIm.getHeight();
        int transparency = stripIm.getColorModel().getTransparency();
        Graphics2D stripGC;

        for (int i = 0; i < numImages; i++) {
          BufferedImage holder = gc.createCompatibleImage(width, height, transparency);  
          list.add(holder);
          stripGC = holder.createGraphics();
          stripGC.drawImage(stripIm, 0, 0, width, height, i * width, 0, (i * width) + width, height, null);
          stripGC.dispose();
        }

        return list;

      }
    }

    return null;
  }

GameSpriteAdvanced class framework example

The loadStripImageArray() method listed above is used in a GameSpriteAdvanced class for the following demonstration:

c) Bats and fireballs using the GameSpriteAdvanced class:

  • Notable features:
    • Elastic collisions with window borders
    • Rectangle intersection collisions between bats and fireballs
    • Sound playback
    • Sprite destruction

cs498gd/java_images_animation_sprites.txt · Last modified: 2016/10/21 18:42 by jchung

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki