cs498gd:java_images_animation_sprites
Table of Contents
Java: Images, Animation, Sprites
Image buffering
drawImage() and Image Quirks
- drawImage() can be weird, in that it does not necessarily draw an Image immediately.
- Under certain circumstances, drawImage() may return without drawing an image.
- 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
- Small example (reads an external image file into a BufferedImage):
- mario.png (external image file)
- Uses threads for animation; see below.
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.
- Demo: Move a line
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 } }
- Demo: generating rectangles
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
- Get the above Java2D code samples in one Eclipse project at https://cssegit.monmouth.edu:2443/jchung/Java2D.git.
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)
- Earlier example (reads an external image file into a BufferedImage):
- Variation on above example, with image scaling and rotation:
- Example above with image scaling and rotation (AffineTransform rotation for image rotation):
GameSprite class framework examples
- Import all 3 example projects below into Eclipse.
- Import from Git, using the following gitlab URI: https://cssegit.monmouth.edu:2443/jchung/GameSpriteClassExamples.git
a) Multiple moving circles and squares using a GameSprite
class:
- Notable features:
- Elastic collisions with window borders
- GameSprite.java (superclass)
- CircleSprite.java (subclass)
- SquareSprite.java (subclass)
- SpriteDemoFrame.java (main program)
-
- Download and import into Eclipse
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?
- 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
-
- It is a good practice to put images (including sprites) and sounds in their own separate directories; well-organized code is always a good practice.
- GameSpriteAdvanced.java (superclass)
- BatAnimatedSprite.java (subclass)
- FireballAnimatedSprite.java (subclass)
- AnimatedSpriteDemo.java (main program)
cs498gd/java_images_animation_sprites.txt · Last modified: 2016/10/21 18:42 by jchung