Wobbly Images in Slick 2D
Whilst creating my Sudoku game, I wanted to create a simple effect on the title of the game. Many year ago, like in the mid 1980′s there were many demos going around on machines like the Spectrum 48k, Commodore 64 and eventually Atari ST and Amiga 500 which would show off sounds and graphics on those machines. One of the most common effects used was to put a SIN wave ripple through an image or text message.
So, with this in mind I decided to write a small method which would take an image, and apply that wobble to it. The process is simple really and made up of just a few steps. First of all, when you create an instance of the ImageBounce you pass in the image you want to bounce and a few other parameters such as x and y location the image is to be shown, the speed of the wobble, the height and also how large each slice of the image should be. The constructor for this is shown below:
public ImageBounce(Image image, int x, int y, float speed, int bounceHeight,
int sliceWidth) throws SlickException {
/* Cut up the supplied image into slices and create an object for
* each of these slices */
slices = new ArrayList<Slice>();
int mx = 0;
int numSlices = image.getWidth() / sliceWidth;
for (int i = 0; i < numSlices; i++) {
Image sliceImage = image.getSubImage(mx, 0, sliceWidth, image.getHeight());
Slice newSlice = new Slice(i, sliceImage, bounceHeight, speed, x + mx, y);
slices.add(newSlice);
mx += sliceWidth;
}
}
The idea behind this is that you slice the image passed up into vertical strips. You can then move each of those strips in relation to each other which allows you to create the effect of a SIN scroll running through the image. The thinner the strips the smoother the image while it is moving etc.
A slice is taken from the main image using the Slick subimage method available within the image class
Image sliceImage = image.getSubImage(mx, 0, sliceWidth, image.getHeight());
This lets you extract one image from inside another. Once I have this new image called sliceImage, I then create a new object called slice. This holds this new image along with details related to it such as its specific x, y location etc.
Slice newSlice = new Slice(i, sliceImage, bounceHeight, speed, x + mx, y);
Once I have a new slice object I add this to the slices ArrayList which is made up of slice objects. I use generics for this to specific the type of objects which are going to be held within my arraylist i.e.
ArrayList<Slice>
Once I have an ArrayList full of Slice objects, I can then just update the location of the object per tick of the game and then render each slice to the screen. This is very simple and done as follows:
/**
* Update the individual characters which make up this message
*
* @param delta
*Â Â Â Â Â Â Â Â Â Â Â milliseconds since last update passed from the main Update
*Â Â Â Â Â Â Â Â Â Â Â method
*/
public void update(int delta) {
for (int i = 0; i < slices.size(); i++) {
Slice c = slices.get(i);
c.update(delta);
}
}
/**
* Render the individual characters to the graphics context
*
* @param g
*Â Â Â Â Â Â Â Â Â Â Â Graphics context to which items should be rendered
*/
public void render(Graphics g) {
for (int i = 0; i < slices.size(); i++) {
Slice c = slices.get(i);
c.render(g);
}
}
That’s about it really. To see this is action check out the current version of my Sudoku game. Its not complete, but you will be able to see the title being wobbled using the code described.
The complete source for the ImageBounce class is shown below
package com.daleyuk.sudoku;
import java.util.ArrayList;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
/**
* @author michael_daley
*
*Â Â Â Â Â Â Â Â Class which takes a message, location and bounce height and then is
*Â Â Â Â Â Â Â Â able to render that message with a SIN bounce running through it.
*/
public class ImageBounce {
private ArrayList<Slice> slices;
public ImageBounce(Image image, int x, int y, float speed, int bounceHeight,
int sliceWidth) throws SlickException {
/* Cut up the supplied image into slices and create an object for
* each of these slices */
slices = new ArrayList<Slice>();
int mx = 0;
int numSlices = image.getWidth() / sliceWidth;
for (int i = 0; i < numSlices; i++) {
Image sliceImage = image.getSubImage(mx, 0, sliceWidth, image.getHeight());
Slice newSlice = new Slice(i, sliceImage, bounceHeight, speed, x + mx, y);
slices.add(newSlice);
mx += sliceWidth;
}
System.out.println(slices.size());
}
/**
* Update the individual characters which make up this message
*
* @param delta
*Â Â Â Â Â Â Â Â Â Â Â milliseconds since last update passed from the main Update
*Â Â Â Â Â Â Â Â Â Â Â method
*/
public void update(int delta) {
for (int i = 0; i < slices.size(); i++) {
Slice c = slices.get(i);
c.update(delta);
}
}
/**
* Render the individual characters to the graphics context
*
* @param g
*Â Â Â Â Â Â Â Â Â Â Â Graphics context to which items should be rendered
*/
public void render(Graphics g) {
for (int i = 0; i < slices.size(); i++) {
Slice c = slices.get(i);
c.render(g);
}
}
/**
* @author michael_daley
*
*Â Â Â Â Â Â Â Â Inner Class which is used to control each individual character in
*Â Â Â Â Â Â Â Â the bouncing message
*/
public class Slice {
private int id;
private Image img;
private int x;
private int y;
private int bounceHeight;
private float speed;
private float counter;
public Slice(int id, Image img, int bounceHeight, float speed, int x, int y) {
this.id = id;
this.img = img;
this.x = x;
this.y = y;
this.bounceHeight = bounceHeight;
this.speed = speed;
}
public void update(int delta) {
/*
* Normally you would update this counter, which controls the speed
* of the bounce using the delta passed in. I've been having
* problems with Delta and vSync being used together. For this
* reason I restrict the FPS to 60 and set the vSync as well. I then
* just upate the counter without using delta and I get smooth
* playback.
*/
counter += speed;
}
public void render(Graphics g) {
/* Sin calculation borrowed from Cute Planet Puzzle by Kev Glass */
img.draw(x, y + (float) (-30 + (Math.sin(counter + (id * 25)) * bounceHeight)));
}
}
}
Let me know if you have any questions.
Mike



