|
|
There are three demos in this set of notes.
The first example can be found at the following URL:
http://www.cs.indiana.edu/
classes/
a348/
t540/
spr2002/
lectures/
code/
Two/
BufferedGraphicsTest.html
To get directly to my applet please click here (IE on
Win32 only.) A better bet is to simply run your
onappletviewer
http://www.cs.indiana.edu/
classes/a348/t540/spr2002/lectures/code/Two/BufferedGraphicsTest.html
(You can also retrive everything from /l/www/classes/a348/t540/spr2002/lectures/code/Two). You've seen the first example already (last week) but the second example is slightly different. Its purpose is to test a utility class that's used relatively frequently. Here's the URL of the example
http://www.cs.indiana.edu/
classes/
a348/
t540/
spr2002/
lectures/
code/
Two/
VectorTest.html
To get directly to my applet please click here (IE on Win32 only.) A better bet is to simply run your
onappletviewer
http://www.cs.indiana.edu/
classes/a348/t540/spr2002/lectures/code/Two/VectorTest.html
Finally, here's an example of the use of the most important part of this set of notes.
http://www.cs.indiana.edu/
classes/
a348/
t540/
spr2002/
lectures/
code/
Two/
ActorTest.html
To get directly to my applet please click here (IE on Win32 only.) A better bet is to simply run your
onappletviewer
http://www.cs.indiana.edu/
classes/a348/t540/spr2002/lectures/code/Two/ActorTest.html
Now a summary of what's needed (in terms of files, listed below):
Actor2D.java
Moveable.java
Vector2D.java
VectorTest.java
VectorTest.html
ImageLoader.java
ImageGroup.java
ActorGroup2D.java
AnimationStrip.java
Animator.java
Robot.java
RobotGroup.java
stile.gif robot.gif robot_dead.gif robot_north.gif robot_south.gif robot_west.gif robot_east.gif
ActorTest.java
RobotAdapter)
ActorTest.html
BufferedGraphics.java
BufferedGraphicsTest.java
BufferedGraphicsTest.html
pipe.gif
StaticActor.java
StaticActorGroup.java
The purpose of this set of notes is to bring together all the files needed so you can build these examples on your own workstations, play with them, look at them, understand and modify them to suit your needs for your projects. We first need (and that's the central point here) an actor class.
No actors, no games.
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
// contains general information for moving and rendering a 2-D game object
public abstract class Actor2D extends Object implements Moveable
{
// some general states an actor might have
public final int STATE_ALIVE = 1;
public final int STATE_DYING = 2;
public final int STATE_DEAD = 4;
// the state of this actor
protected int state;
// the actor group this actor belongs to
protected ActorGroup2D group;
// position, velocity, and rotation of the actor,
// along with cached transformation
protected Vector2D pos;
protected Vector2D vel;
protected double rotation;
protected AffineTransform xform;
protected final double TWO_PI = 2*Math.PI;
// a bounding rectangle for things such as collision testing
protected Rectangle2D bounds;
// a list of actors this actor has collided with during one frame
protected LinkedList collisionList;
// width and height of this actor
protected int frameWidth;
protected int frameHeight;
// reference to this actor's current animation strip
protected AnimationStrip currAnimation;
// number of frames to wait before animating the next frame,
// plus a wait counter
protected int animWait;
protected int animCount;
// creates a new Actor2D object belonging to the given ActorGroup
public Actor2D(ActorGroup2D grp)
{
group = grp;
bounds = new Rectangle2D.Double();
collisionList = new LinkedList();
state = 0;
pos = new Vector2D.Double();
vel = new Vector2D.Double();
rotation = 0;
xform = new AffineTransform();
currAnimation = null;
animWait = 0;
animCount = 0;
frameWidth = 0;
frameHeight = 0;
}
// animates the actor every animWait frames
public void animate()
{
if(currAnimation != null)
{
if(++animCount >= animWait)
{
currAnimation.getNextFrame();
animCount = 0;
}
}
}
// draws the actor using its native transformation
public void paint(Graphics2D g2d)
{
if(currAnimation != null)
{
g2d.drawImage(currAnimation.getCurrFrame(),
xform,
AnimationStrip.observer);
}
}
// draws the actor at the sent x,y coordinates
public void paint(Graphics2D g2d, double x, double y)
{
if(currAnimation != null)
{
g2d.drawImage(currAnimation.getCurrFrame(),
AffineTransform.getTranslateInstance(x, y),
AnimationStrip.observer
);
}
}
// simple bounding-box determination of whether this actor
// has collided with the sent actor
public boolean intersects(Actor2D other)
{
return bounds.intersects(other.getBounds());
}
// updates the bounding rectangle of this actor to meet its
// current x and y positions
public void updateBounds()
{
// make sure we know the correct width and height of the actor
if(frameWidth <= 0 && currAnimation != null)
{
frameWidth = currAnimation.getFrameWidth();
}
if(frameHeight <= 0 && currAnimation != null)
{
frameHeight = currAnimation.getFrameHeight();
}
bounds.setRect(pos.getX(), pos.getY(), frameWidth, frameHeight);
}
// makes sure that the actor's bounds have not exceeded the bounds
// specified by its actor group
public void checkBounds()
{
if(group == null) return;
if(bounds.getX() < group.MIN_X_POS)
{
pos.setX(group.MIN_X_POS);
}
else if(bounds.getX() + frameWidth > group.MAX_X_POS)
{
pos.setX(group.MAX_X_POS - frameWidth);
}
if(bounds.getY() < group.MIN_Y_POS)
{
pos.setY(group.MIN_Y_POS);
}
else if(bounds.getY() + frameHeight > group.MAX_Y_POS)
{
pos.setY(group.MAX_Y_POS - frameHeight);
}
}
// returns a String representation of this actor
public String toString()
{
return super.toString();
}
// bitwise OR's the sent attribute state with the current attribute state
public final void setState(int attr)
{
state |= attr;
}
// resets an attribute using the bitwise AND and NOT operators
public final void resetState(int attr)
{
state &= ~attr;
}
public final int getState()
{
return state;
}
public final void clearState()
{
state = 0;
}
// determines if the sent state attribute is contained in this
// actor's state attribute
public final boolean hasState(int attr)
{
return ((state & attr) != 0);
}
// access methods for the velocity, position, and rotation of the actor
public final void setX(double px)
{
pos.setX(px);
}
public final void setY(double py)
{
pos.setY(py);
}
public final double getX()
{
return pos.getX();
}
public final double getY()
{
return pos.getY();
}
public final void setPos(int x, int y)
{
pos.setX(x);
pos.setY(y);
}
public final void setPos(double x, double y)
{
pos.setX(x);
pos.setY(y);
}
public final void setPos(Vector2D v)
{
pos.setX(v.getX());
pos.setY(v.getY());
}
public final Vector2D getPos()
{
return pos;
}
public final void setRot(double theta)
{
rotation = theta;
}
public final double getRot()
{
return rotation;
}
public final void rotate(double theta)
{
rotation += theta;
while(rotation > TWO_PI)
{
rotation -= TWO_PI;
}
while(rotation < -TWO_PI)
{
rotation += TWO_PI;
}
}
public final void setVel(int x, int y)
{
vel.setX(x);
vel.setY(y);
}
public final void setVel(Vector2D v)
{
vel.setX(v.getX());
vel.setY(v.getY());
}
public final Vector2D getVel()
{
return vel;
}
public final void moveBy(double x, double y)
{
pos.translate(x, y);
}
public final void moveBy(int x, int y)
{
pos.translate(x, y);
}
public final void moveBy(Vector2D v)
{
pos.translate(v);
}
public final void accelerate(double ax, double ay)
{
vel.setX(vel.getX() + ax);
vel.setY(vel.getY() + ay);
}
public int getWidth()
{
return frameWidth;
}
public int getHeight()
{
return frameHeight;
}
// methods inherited from the Moveable interface
public Rectangle2D getBounds()
{
return bounds;
}
// determines if a Moveable object has collided with this object
public boolean collidesWith(Moveable other)
{
return (bounds.contains(other.getBounds()) ||
bounds.intersects(other.getBounds()));
}
// adds a collision object to this collision list
public void addCollision(Moveable other)
{
if(collisionList == null)
{
collisionList = new LinkedList();
collisionList.add(other);
return;
}
if(! collisionList.contains(other))
{
collisionList.add(other);
}
}
// stub method for processing collisions with those
// actors contained within the collisionsList
// this method is left empty, but not abstract
public void processCollisions()
{
}
// updates the object's position and bounding box, animates it,
// then updates the transformation
public void update()
{
pos.translate(vel);
updateBounds();
checkBounds();
animate();
// subclasses which require the transformation to be
// centered about an anchor point other than the position
// will need to override this method
if(rotation != 0)
{
xform.setToIdentity();
xform.translate(pos.getX()+frameWidth/2,
pos.getY()+frameHeight/2);
xform.rotate(rotation);
xform.translate(-frameWidth/2, -frameHeight/2);
}
else
{
xform.setToTranslation(pos.getX(), pos.getY());
}
}
} // Actor2D
You also need this interface.
import java.awt.geom.*;
public interface Moveable
{
public Rectangle2D getBounds();
public boolean collidesWith(Moveable other);
public void addCollision(Moveable other);
public void processCollisions();
public void update();
} // Moveable
We cannot create actors just yet. Why? (For at least two reasons).
The following class provides support for the Actor2D class.
public abstract class Vector2D extends Object
{
public static final Vector2D.Double ZERO_VECTOR
= new Vector2D.Double(0, 0);
public static class Double extends Vector2D
{
// the x and y components of this Vector2D.Double object
public double x;
public double y;
// creates a default Vector2D with a value of (0,0)
public Double()
{
this(0.0, 0.0);
}
// creates a Vector2D.Double object with the sent values
public Double(double m, double n)
{ setX(m);
setY(n);
}
private Double(int m, int n)
{ setX((double) m);
setY((double) n);
}
// get/set access methods for the x and y components
public final void setX(double n)
{ x = n;
}
public final void setY(double n)
{ y = n;
}
public final double getX()
{ return x;
}
public final double getY()
{ return y;
}
// adds this vector to the sent vector
public Vector2D plus(Vector2D v)
{
return new Double(getX() + v.getX(), getY() + v.getY());
}
// subtracts the sent vector from this vector
public Vector2D minus(Vector2D v)
{
return new Double(getX() - v.getX(), getY() - v.getY());
}
} // Double
public static class Integer extends Vector2D
{
// the x and y components of this Vector2D.Integer object
public int x;
public int y;
// creates a default Vector2D with a value of (0,0)
public Integer()
{
this(0, 0);
}
// creates a Vector2D.Integer object with the sent values
public Integer(int m, int n)
{ setX(m);
setY(n);
}
private Integer(double m, double n)
{ setX((int) m);
setY((int) n);
}
// get/set access methods for the x and y components
public final void setX(double n)
{ x = (int) n;
}
public final void setY(double n)
{ y = (int) n;
}
public final double getX()
{ return (double) x;
}
public final double getY()
{ return (double) y;
}
// adds this vector to the sent vector
public Vector2D plus(Vector2D v)
{
return new Integer(getX() + v.getX(), getY() + v.getY());
}
// subtracts the sent vector from this vector
public Vector2D minus(Vector2D v)
{
return new Integer(getX() - v.getX(), getY() - v.getY());
}
} // Integer
protected Vector2D() { }
public abstract void setX(double n);
public abstract void setY(double n);
public abstract double getX();
public abstract double getY();
// adds this vector to the sent vector
public abstract Vector2D plus(Vector2D v);
// subtracts the sent vector from this vector
public abstract Vector2D minus(Vector2D v);
// determines if two vectors are equal
public boolean equals(Vector2D other)
{
return (getX() == other.getX() && getY() == other.getY());
}
// normalizes this vector to unit length
public void normalize()
{
double len = length();
setX(getX() / len);
setY(getY() / len);
}
public void scale(double k)
{
setX(k * getX());
setY(k * getY());
}
// translates the Vector2D by the sent value
public void translate(double dx, double dy)
{
setX(getX() + dx);
setY(getY() + dy);
}
// translates the Vector2D by the sent vector
public void translate(Vector2D v)
{
setX(getX() + v.getX());
setY(getY() + v.getY());
}
// calculates the dot (or inner) product of this and the sent vector
public double dot(Vector2D v)
{
return getX()*v.getX() + getY()*v.getY();
}
// returns the length of this vector
public double length()
{
return Math.sqrt(this.dot(this));
}
// returns the String representation of this Vector2D
public String toString()
{
return getClass().getName() + " [x=" + getX() + ",y=" + getY() + "]";
}
} // Vector2D
Then a simple test, to prove the concept.
import java.applet.*;
import java.awt.*;
import java.util.*;
public class VectorTest extends Applet implements Runnable
{
// an array of vector positions
private Vector2D[] vects;
// an array of velocity values for the above array
private Vector2D[] vels;
// colors used to draw lines
private final Color[] COLORS = {
Color.blue, Color.red, Color.green, Color.darkGray,
Color.black, Color.orange, Color.pink, Color.cyan
};
// a thread for animation
private Thread animation;
// an offscreen rendering image
private Image offscreen;
public void init()
{
int len = COLORS.length;
vects = new Vector2D[len];
vels = new Vector2D[len];
Random r = new Random();
for(int i = 0; i < len; i++)
{
// create points that make up an circle
vects[i]
= new Vector2D.Double
(50*(Math.cos(Math.toRadians(i*(360/len)))),
50*(Math.sin(Math.toRadians(i*(360/len)))));
// translate the point to the center of the screen
vects[i].translate(getSize().width/2, getSize().height/2);
vels[i] = new Vector2D.Integer(1 + r.nextInt()%5,
1 + r.nextInt()%5);
}
offscreen = createImage(getSize().width, getSize().height);
animation = new Thread(this);
}
public void start()
{
animation.start();
}
public void stop()
{
animation = null;
}
public void run()
{
Thread t = Thread.currentThread();
while(t == animation)
{
repaint();
try
{
t.sleep(20);
}
catch(InterruptedException e)
{
}
}
}
public void update(Graphics g)
{
// save the width and height of the applet window
double width = (double) getSize().width;
double height = (double) getSize().height;
for(int i = 0; i < COLORS.length; i++)
{
vects[i].translate(vels[i].getX(), vels[i].getY());
if(vects[i].getX() > width)
{
vects[i].setX(width);
vels[i].setX(-vels[i].getX());
}
else if(vects[i].getX() < 0)
{
vects[i].setX(0);
vels[i].setX(-vels[i].getX());
}
if(vects[i].getY() > height)
{
vects[i].setY(height);
vels[i].setY(-vels[i].getY());
}
else if(vects[i].getY() < 0)
{
vects[i].setY(0);
vels[i].setY(-vels[i].getY());
}
}
if(offscreen == null ||
offscreen.getWidth(null) != getSize().width ||
offscreen.getHeight(null) != getSize().height)
{
offscreen = createImage(getSize().width, getSize().height);
}
paint(g);
}
public void paint(Graphics g)
{
// cast the sent Graphics context to get a usable Graphics2D object
Graphics2D g2d = (Graphics2D)offscreen.getGraphics();
g2d.setPaint(Color.white);
g2d.fillRect(0, 0, getSize().width, getSize().height);
g2d.setStroke(new BasicStroke(3.0f));
// connect all of the lines together
Vector2D prev = vects[COLORS.length-1];
for(int i = 0; i < COLORS.length; i++)
{
g2d.setPaint(COLORS[i]);
g2d.drawLine((int) prev.getX(), (int) prev.getY(),
(int) vects[i].getX(), (int) vects[i].getY());
prev = vects[i];
}
g.drawImage(offscreen, 0, 0, this);
}
} // VectorTest
And here's an HTML file in case you want to save some typing.
<html>
<head>
<title>VectorTest</title>
</head>
<body>
<hr>
<applet code=VectorTest.class width=300 height=300></applet>
<hr>
</body>
</html>
Now we start providing the missing classes and build an example.
Here's the file ImageLoader.java
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
public class ImageLoader extends Object
{
// an Applet to load and observe loading images
protected Applet applet;
// an Image, along with its width and height
protected Image image;
protected int imageWidth;
protected int imageHeight;
// a buffer to render images to immediately after they are loaded
protected static BufferedImage buffer
= new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
public ImageLoader(
Applet a, // creates and observes loading images
String filename, // name of image to load on disk
boolean wait // if true, add to a MediaTracker
// object and wait to be loaded
)
{
applet = a;
image = applet.getImage(applet.getDocumentBase(), filename);
if(wait)
{
// create a new MediaTracker object for this image
MediaTracker mt = new MediaTracker(applet);
// load the strip image
mt.addImage(image, 0);
try
{
// wait for our main image to load
mt.waitForID(0);
}
catch(InterruptedException e) { /* do nothing */ }
}
// get the width and height of the image
imageWidth = image.getWidth(applet);
imageHeight = image.getHeight(applet);
}
public int getImageWidth()
{
return imageWidth;
}
public int getImageHeight()
{
return imageHeight;
}
public Image getImage()
{
return image;
}
// extracts a cell from the image using an image filter
public Image extractCell(int x, int y, int width, int height)
{
// get the ImageProducer source of our main image
ImageProducer sourceProducer = image.getSource();
Image cell = applet.createImage
(new FilteredImageSource(sourceProducer,
new CropImageFilter
(x, y, width, height)));
// draw the cell to the off-screen buffer
buffer.getGraphics().drawImage(cell, 0, 0, applet);
return cell;
}
// extracts a cell from the image and scales
// it to the sent width and height
public Image extractCellScaled
(int x, int y, int width, int height, int sw, int sh)
{
// get the ImageProducer source of our main image
ImageProducer sourceProducer = image.getSource();
Image cell = applet.createImage
(new FilteredImageSource(sourceProducer,
new CropImageFilter
(x, y, width, height)));
// draw the cell to the off-screen buffer
buffer.getGraphics().drawImage(cell, 0, 0, applet);
return cell.getScaledInstance(sw, sh, Image.SCALE_SMOOTH);
}
} // ImageLoader
Next, we provide the file ImageGroup.java
import java.applet.*;
// provides methods for creating and accessing AnimationStrip objects
public abstract class ImageGroup
{
// an array of AnimationStrip objects that create
// our animation sequences as a whole
protected AnimationStrip[] animations;
// the width and height of an individual image frame
protected int frameWidth;
protected int frameHeight;
// creates a new ImageGroup object
protected ImageGroup()
{
animations = null;
}
// initializes the ImageGroup using the sent Applet reference object
public abstract void init(Applet a);
public final int getFrameWidth()
{
return frameWidth;
}
public final int getFrameHeight()
{
return frameHeight;
}
// accesses the AnimationStrip at the given index
public final AnimationStrip getAnimationStrip(int index)
{
if(animations != null)
{
try
{
return animations[index];
}
catch(ArrayIndexOutOfBoundsException e)
{
// send error to debugger or standard output...
}
}
return null;
}
} // ImageGroup
Here's file ActorGroup2D.java
import java.applet.*;
// defines related attributes common to Actor2D objects
public abstract class ActorGroup2D extends ImageGroup
{
// default min/max values for int's and float's
protected static final int MAX_INT_UNBOUND = Integer.MAX_VALUE;
protected static final int MIN_INT_UNBOUND = Integer.MIN_VALUE;
protected static final double MAX_DBL_UNBOUND = Double.MAX_VALUE;
protected static final double MIN_DBL_UNBOUND = Double.MIN_VALUE;
// the maximum and minimum position and velocity an Actor2D can have
// overriding classes can change these values at construction time or
// within the init method
public int MAX_X_POS = MAX_INT_UNBOUND;
public int MAX_Y_POS = MAX_INT_UNBOUND;
public int MIN_X_POS = MIN_INT_UNBOUND;
public int MIN_Y_POS = MIN_INT_UNBOUND;
public int MAX_X_VEL = MAX_INT_UNBOUND;
public int MAX_Y_VEL = MAX_INT_UNBOUND;
public int MIN_X_VEL = MIN_INT_UNBOUND;
public int MIN_Y_VEL = MIN_INT_UNBOUND;
// constructs a new ActorGroup2D object
protected ActorGroup2D()
{
super();
}
// initializes shared Actor2D attributes
public abstract void init(Applet a);
} // ActorGroup2D
Here's AnimationStrip.java
import java.awt.*;
import java.awt.image.*;
import java.util.*;
// defines a dynamic list of Image frames that
// can be animated using a given Animator object
public class AnimationStrip extends Object
{
// observes drawing for external objects
public static ImageObserver observer;
// a linked list of Image frames, along with the size of the list
protected LinkedList frames;
protected int numFrames;
// the Animator responsible for animating frames
protected Animator animator;
// creates a new AnimationStrip object
public AnimationStrip()
{
frames = null;
numFrames = 0;
animator = null;
}
public final void setAnimator(Animator anim)
{
animator = anim;
animator.setFrames(frames);
}
// adds an Image frame to the list
public void addFrame(Image i)
{
if(frames == null)
{
frames = new LinkedList();
numFrames = 0;
}
frames.add(i);
numFrames++;
}
// returns the Animator's current frame
public Image getCurrFrame()
{
if(frames != null)
{
return animator.getCurrFrame();
}
return null;
}
// allows the Animator to generate the next frame of animation
public void animate()
{
if(animator != null)
{
animator.nextFrame();
}
}
// returns the Animator's next frame of animation
public Image getNextFrame()
{
if(animator != null)
{
animator.nextFrame();
return animator.getCurrFrame();
}
return null;
}
// returns the first frame of animation
public Image getFirstFrame()
{
if(frames != null)
{
return (Image)frames.getFirst();
}
return null;
}
// returns the last frame of animation
public Image getLastFrame()
{
if(frames != null)
{
return (Image)frames.getLast();
}
return null;
}
// resets the Animator's internal animation sequence
public void reset()
{
if(animator != null)
{
animator.reset();
}
}
// returns an animation frame's width
public int getFrameWidth()
{
if(frames != null && !frames.isEmpty())
{
return getFirstFrame().getWidth(observer);
}
return 0;
}
// returns an animation frame's height
public int getFrameHeight()
{
if(frames != null && !frames.isEmpty())
{
return getFirstFrame().getHeight(observer);
}
return 0;
}
} // AnimationStrip
Here's Animator.java
import java.awt.*;
import java.util.*;
// defines a custom way of animating a list of Image frames
public abstract class Animator extends Object
{
// references a linked list of Image frames
protected LinkedList frames;
// the current index of animation
protected int currIndex;
// creates a new Animator object with a null-referenced set of frames
protected Animator()
{
frames = null;
currIndex = 0;
}
public final void setFrames(LinkedList list)
{
frames = list;
}
// resets this animation
public void reset()
{
currIndex = 0;
}
// returns the current frame of animation
public Image getCurrFrame()
{
if(frames != null)
{
return (Image)frames.get(currIndex);
}
return null;
}
// this method defines how frames are animated
public abstract void nextFrame();
// animates frames based on a sent array of indices
public static class Indexed extends Animator
{
protected int[] indices;
protected int arrayIndex;
public Indexed()
{
super();
arrayIndex = 0;
}
public Indexed(int[] idx)
{
indices = new int[idx.length];
System.arraycopy(idx, 0, indices, 0, idx.length);
arrayIndex = 0;
}
public void nextFrame()
{
if(frames != null)
{
// increments the index counter
if(++arrayIndex >= indices.length)
{
arrayIndex = 0;
}
currIndex = indices[arrayIndex];
}
}
} // Animator.Indexed
// iterates through the animation frames, looping
// back to the start when necessary
public static class Looped extends Animator
{
public Looped()
{
super();
}
public void nextFrame()
{
if(frames != null)
{
if(++currIndex >= frames.size())
{
reset();
}
}
}
} // Animator.Looped
// iterates through the animation frames, but
// stops once it reaches the last frame
public static class OneShot extends Animator
{
public OneShot()
{
super();
}
public void nextFrame()
{
if(frames != null)
{
if(++currIndex >= frames.size());
{
currIndex = frames.size()-1;
}
}
}
} // Animator.OneShot
// generates a random animation frame during each call to nextFrame
public static class Random extends Animator
{
private java.util.Random random;
public Random()
{
super();
random = new java.util.Random();
}
public void nextFrame()
{
if(frames != null)
{
currIndex = random.nextInt() % frames.size();
}
}
} // Animator.Random
// represents an animation containing only one frame--
// this class saves time since it does not processing
public static class Single extends Animator
{
public Single()
{
super();
}
public void nextFrame()
{
// do nothing...
}
} // Animator.Single
} // Animator
Here's Robot.java
import java.awt.*;
// creates a simple robot that can move in the ordinal directions as well as
// fire its weapon
public class Robot extends Actor2D
{
// index correlating to the current animation
protected int currAnimIndex;
// saves the previous animation for shooting animations
protected int prevAnimIndex;
// the Robot state SHOOTING
public final static int SHOOTING = 8;
// used to tell the robot in which direction to move
public final static int DIR_NORTH = 0;
public final static int DIR_SOUTH = 1;
public final static int DIR_EAST = 2;
public final static int DIR_WEST = 3;
// creates a new Robot with the given actor group
public Robot(ActorGroup2D grp)
{
super(grp);
vel.setX(5);
vel.setY(5);
animWait = 3;
currAnimIndex = 0;
prevAnimIndex = 0;
currAnimation = group.getAnimationStrip(RobotGroup.WALKING_SOUTH);
frameWidth = currAnimation.getFrameWidth();
frameHeight = currAnimation.getFrameHeight();
}
// updates the position of the robot, and animates it if it is shooting
public void update()
{
if(hasState(SHOOTING))
{
animate();
}
xform.setToTranslation(pos.getX(), pos.getY());
updateBounds();
checkBounds();
}
// flags the robot to shoot until stopShooting is called
public void startShooting()
{
prevAnimIndex = currAnimIndex;
if((currAnimIndex % 2) == 0)
{
currAnimIndex++;
}
currAnimation = group.getAnimationStrip(currAnimIndex);
currAnimation.reset();
setState(SHOOTING);
}
// stops shooting and restores the previous animation
public void stopShooting()
{
currAnimIndex = prevAnimIndex;
currAnimation = group.getAnimationStrip(currAnimIndex);
currAnimation.reset();
resetState(SHOOTING);
}
// moves and animates the robot based on the sent ordinal direction
public void move(int dir)
{
// prevent further shooting
resetState(SHOOTING);
switch(dir)
{
case DIR_NORTH:
if(currAnimIndex != RobotGroup.WALKING_NORTH)
{
prevAnimIndex = currAnimIndex;
currAnimation = group.getAnimationStrip(
RobotGroup.WALKING_NORTH);
currAnimIndex = RobotGroup.WALKING_NORTH;
currAnimation.reset();
}
else
{
animate();
pos.translate(0, -vel.getY());
}
break;
case DIR_SOUTH:
if(currAnimIndex != RobotGroup.WALKING_SOUTH)
{
prevAnimIndex = currAnimIndex;
currAnimation = group.getAnimationStrip(
RobotGroup.WALKING_SOUTH);
currAnimIndex = RobotGroup.WALKING_SOUTH;
currAnimation.reset();
}
else
{
animate();
pos.translate(0, vel.getY());
}
break;
case DIR_WEST:
if(currAnimIndex != RobotGroup.WALKING_WEST)
{
prevAnimIndex = currAnimIndex;
currAnimation = group.getAnimationStrip(
RobotGroup.WALKING_WEST);
currAnimIndex = RobotGroup.WALKING_WEST;
currAnimation.reset();
}
else
{
animate();
pos.translate(-vel.getX(), 0);
}
break;
case DIR_EAST:
if(currAnimIndex != RobotGroup.WALKING_EAST)
{
prevAnimIndex = currAnimIndex;
currAnimation = group.getAnimationStrip(
RobotGroup.WALKING_EAST);
currAnimIndex = RobotGroup.WALKING_EAST;
currAnimation.reset();
}
else
{
animate();
pos.translate(vel.getX(), 0);
}
break;
default:
break;
}
}
} // Robot
Here's RobotGroup.java
import java.applet.*;
public class RobotGroup extends ActorGroup2D
{
// indices to pre-defined animation sequences
public static final int WALKING_NORTH = 0;
public static final int SHOOTING_NORTH = 1;
public static final int WALKING_SOUTH = 2;
public static final int SHOOTING_SOUTH = 3;
public static final int WALKING_EAST = 4;
public static final int SHOOTING_EAST = 5;
public static final int WALKING_WEST = 6;
public static final int SHOOTING_WEST = 7;
// creates a new RobotGroup object
public RobotGroup()
{
super();
animations = new AnimationStrip[8];
}
// initializes the eight animation sequences
public void init(Applet a)
{
ImageLoader loader;
int i;
// NORTH
loader = new ImageLoader(a, "robot_north.gif", true);
animations[WALKING_NORTH] = new AnimationStrip();
for(i = 0; i < 4; i++)
{
animations[WALKING_NORTH].
addFrame(loader.extractCell((i*72)+(i+1), 1, 72, 80));
}
animations[WALKING_NORTH].setAnimator(new Animator.Looped());
animations[SHOOTING_NORTH] = new AnimationStrip();
for(i = 0; i < 2; i++)
{
animations[SHOOTING_NORTH].
addFrame(loader.extractCell((i*72)+(i+1), 82, 72, 80));
}
animations[SHOOTING_NORTH].setAnimator(new Animator.Looped());
// SOUTH
loader = new ImageLoader(a, "robot_south.gif", true);
animations[WALKING_SOUTH] = new AnimationStrip();
for(i = 0; i < 4; i++)
{
animations[WALKING_SOUTH].
addFrame(loader.extractCell((i*72)+(i+1), 1, 72, 80));
}
animations[WALKING_SOUTH].setAnimator(new Animator.Looped());
animations[SHOOTING_SOUTH] = new AnimationStrip();
for(i = 0; i < 2; i++)
{
animations[SHOOTING_SOUTH].
addFrame(loader.extractCell((i*72)+(i+1), 82, 72, 80));
}
animations[SHOOTING_SOUTH].setAnimator(new Animator.Looped());
// EAST
loader = new ImageLoader(a, "robot_east.gif", true);
animations[WALKING_EAST] = new AnimationStrip();
for(i = 0; i < 4; i++)
{
animations[WALKING_EAST].
addFrame(loader.extractCell((i*72)+(i+1), 1, 72, 80));
}
animations[WALKING_EAST].setAnimator(new Animator.Looped());
animations[SHOOTING_EAST] = new AnimationStrip();
for(i = 0; i < 2; i++)
{
animations[SHOOTING_EAST].
addFrame(loader.extractCell((i*72)+(i+1), 82, 72, 80));
}
animations[SHOOTING_EAST].setAnimator(new Animator.Looped());
// WEST
loader = new ImageLoader(a, "robot_west.gif", true);
animations[WALKING_WEST] = new AnimationStrip();
for(i = 0; i < 4; i++)
{
animations[WALKING_WEST].
addFrame(loader.extractCell((i*72)+(i+1), 1, 72, 80));
}
animations[WALKING_WEST].setAnimator(new Animator.Looped());
animations[SHOOTING_WEST] = new AnimationStrip();
for(i = 0; i < 2; i++)
{
animations[SHOOTING_WEST].
addFrame(loader.extractCell((i*72)+(i+1), 82, 72, 80));
}
animations[SHOOTING_WEST].setAnimator(new Animator.Looped());
}
} // RobotGroup2D
Now a number of .gif files may be needed:
Here's file ActorTest.java (contains RobotAdapter)
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.*;
// adapter class for controlling a Robot object
class RobotAdapter extends KeyAdapter
{
private Robot robot;
public RobotAdapter(Robot r)
{
robot = r;
}
// fires the robot gun or moves the robot
public void keyPressed(KeyEvent e)
{
robot.resetState(Robot.SHOOTING);
switch(e.getKeyCode())
{
case KeyEvent.VK_SPACE:
robot.startShooting();
break;
case KeyEvent.VK_UP:
robot.move(Robot.DIR_NORTH);
break;
case KeyEvent.VK_DOWN:
robot.move(Robot.DIR_SOUTH);
break;
case KeyEvent.VK_LEFT:
robot.move(Robot.DIR_WEST);
break;
case KeyEvent.VK_RIGHT:
robot.move(Robot.DIR_EAST);
break;
default:
break;
}
}
// if the space bar is relesed, stop the robot from shooting
public void keyReleased(KeyEvent e)
{
if(e.getKeyCode() == KeyEvent.VK_SPACE)
{
robot.stopShooting();
}
}
} // RobotAdapter
public class ActorTest extends Applet implements Runnable
{
// a thread for animation
private Thread animation;
// an offscreen rendering buffer
private BufferedGraphics offscreen;
// a Paint for drawing a tiled background
Paint paint;
// geometry for filling the background
private Rectangle2D floor;
// our moveable robot
private Robot robot;
public void init()
{
// create the RobotGroup
RobotGroup group = new RobotGroup();
group.init(this);
// set the Robot bounds equal to the window size
group.MIN_X_POS = 0;
group.MIN_Y_POS = 0;
group.MAX_X_POS = getSize().width;
group.MAX_Y_POS = getSize().height;
// create our robot in the center of the screen
robot = new Robot(group);
robot.setPos((getSize().width - robot.getWidth()) /2,
(getSize().height - robot.getHeight())/2);
// register a new RobotAdapter to receive Robot movement commands
addKeyListener(new RobotAdapter(robot));
// create the background paint
createPaint();
offscreen = new BufferedGraphics(this);
AnimationStrip.observer = this;
animation = new Thread(this);
} // init
// create a tiled background paint
private void createPaint()
{
Image image = getImage(getDocumentBase(), "stile.gif");
while(image.getWidth(this) <= 0);
// create a new BufferedImage with the image's width and height
BufferedImage bi = new BufferedImage(
image.getWidth(this),
image.getHeight(this),
BufferedImage.TYPE_INT_RGB);
// get the Graphics2D context of the BufferedImage and
// render the original image onto it
((Graphics2D)bi.getGraphics()).drawImage(image,
new AffineTransform(),
this);
// create the anchoring rectangle for the paint's
// image equal in size to the image's size
floor = new Rectangle2D.Double(0,
0,
getSize().width,
getSize().height);
// set the paint
paint = new TexturePaint(bi,
new Rectangle(0,
0,
image.getWidth(this),
image.getHeight(this)));
}
public void start()
{
// start the animation thread
animation.start();
}
public void stop()
{
animation = null;
}
public void run()
{
Thread t = Thread.currentThread();
while (t == animation)
{
try
{
Thread.sleep(10);
}
catch(InterruptedException e)
{
break;
}
repaint();
}
} // run
public void update(Graphics g)
{
robot.update();
paint(g);
}
public void paint(Graphics g)
{
Graphics2D bg = (Graphics2D)offscreen.getValidGraphics();
// set the paint and fill the background
bg.setPaint(paint);
bg.fill(floor);
// paint the robot
robot.paint(bg);
// draw the offscreen image to the window
g.drawImage(offscreen.getBuffer(), 0, 0, this);
} // paint
} // ActorTest
Here's ActorTest.html now:
<html>
<head>
<title>ActorTest</title>
</head>
<body>
<hr>
<applet code=ActorTest.class width=300 height=300></applet>
<hr>
</body>
</html>
You should already have BufferedGraphics.java but here it is anyway:
import java.applet.*;
import java.awt.*;
public class BufferedGraphics extends Object
{
// the Component that will be drawing the offscreen image
protected Component parent;
// the offscreen rendering Image
protected Image buffer;
// creates a new BufferedGraphics object
protected BufferedGraphics()
{
parent = null;
buffer = null;
}
// creates a new BufferedGraphics object with the sent parent Component
public BufferedGraphics(Component c)
{
parent = c;
createBuffer();
}
public final Image getBuffer()
{
return buffer;
}
// returns the buffer's Graphics context after
// the buffer has been validated
public Graphics getValidGraphics()
{
if(! isValid())
{
createBuffer();
}
return buffer.getGraphics();
}
// creates an offscreen rendering image matching
// the parent's width and height
protected void createBuffer()
{
Dimension size = parent.getSize();
buffer = parent.createImage(size.width, size.height);
}
// validates the offscreen image against several criteria, namely
// against the null reference and the parent's width and height
protected boolean isValid()
{
if(parent == null)
{
return false;
}
Dimension s = parent.getSize();
if(buffer == null ||
buffer.getWidth(null) != s.width ||
buffer.getHeight(null) != s.height)
{
return false;
}
return true;
}
} // BufferedGraphics
You also should already have BufferedGraphicsTest.java but here it is anyway:
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
public class BufferedGraphicsTest extends Applet implements Runnable
{
// a Thread for animation
private Thread animation;
// the offscreen rendering image
private BufferedGraphics offscreen;
// an Image to render
private Image pipe;
// the width and height of one Image frame
private int imageWidth;
private int imageHeight;
// the position, velocity, and rotation of the Image object
private int x;
private int y;
private int vx;
private int vy;
private double rot;
private AffineTransform at;
private final double ONE_RADIAN = Math.toRadians(10);
public void init()
{
// create the image to render
pipe = getImage(getDocumentBase(), "pipe.gif");
while(pipe.getWidth(this) <= 0);
// create the offscreen image
offscreen = new BufferedGraphics(this);
imageWidth = pipe.getWidth(this);
imageHeight = pipe.getHeight(this);
vx = 3+(int)(Math.random()*5);
vy = 3+(int)(Math.random()*5);
x = getSize().width/2 - imageWidth/2;
y = getSize().height/2 - imageHeight/2;
rot = 0;
at = AffineTransform.getTranslateInstance(x, y);
} // init
public void start()
{
// start the animation thread
animation = new Thread(this);
animation.start();
}
public void stop()
{
animation = null;
}
public void run()
{
Thread t = Thread.currentThread();
while (t == animation)
{
try
{
Thread.sleep(33);
}
catch(InterruptedException e)
{
break;
}
repaint();
}
} // run
public void update(Graphics g)
{
// update the object's position
x += vx;
y += vy;
// keep the object within our window
if(x < 0)
{
x = 0;
vx = -vx;
}
else if(x > getSize().width - imageWidth)
{
x = getSize().width - imageWidth;
vx = -vx;
}
if(y < 0)
{
y = 0;
vy = -vy;
}
else if(y > getSize().height - imageHeight)
{
y = getSize().height - imageHeight;
vy = -vy;
}
if(vx > 0)
rot += ONE_RADIAN;
else
rot -= ONE_RADIAN;
// set the transform for the image
at.setToIdentity();
at.translate(x + imageWidth/2, y + imageHeight/2);
at.rotate(rot);
at.translate(-imageWidth/2, -imageHeight/2);
paint(g);
}
public void paint(Graphics g)
{
// validate and clear the offscreen image
Graphics2D bg = (Graphics2D)offscreen.getValidGraphics();
bg.setColor(Color.black);
bg.fill(new Rectangle(getSize().width, getSize().height));
// draw the pipe to the offscreen image
bg.drawImage(pipe, at, this);
// draw the offscreen image to the applet window
g.drawImage(offscreen.getBuffer(), 0, 0, this);
} // paint
} // BufferedGraphicsTest
Here's pipe.gif.
We should also note StaticActor.java
import java.awt.*;
public class StaticActor extends Actor2D
{
public StaticActor(ActorGroup2D grp)
{
super(grp);
// just reference the 0th (and only) animation strip
currAnimation = group.getAnimationStrip(0);
frameWidth = currAnimation.getFrameWidth();
frameHeight = currAnimation.getFrameHeight();
}
} // StaticActor
We should also make a note of StaticActorGroup.java now:
import java.applet.*;
public class StaticActorGroup extends ActorGroup2D
{
private String filename;
protected StaticActorGroup()
{
filename = null;
}
public StaticActorGroup(String fn)
{
filename = fn;
animations = new AnimationStrip[1];
}
public void init(Applet a)
{
animations[0] = new AnimationStrip();
Image image = a.getImage(a.getCodeBase(), filename);
while(image.getWidth(a) <= 0);
animations[0].addFrame(image);
animations[0].setAnimator(new Animator.Single());
}
} // StaticActorGroup
That's basically it for this chapter (10, an Actor2D class).
A290