![]() |
![]() Java Gaming for the Masses
An |
There are three demos in this set of notes.
The first example can be found at the following URL:
To get directly to my applet please click here (IE on Win32 only.)http://www.cs.indiana.edu/ classes/ a348/ t540/ spr2002/ lectures/ code/ Two/ BufferedGraphicsTest.html
A better bet is to simply run your
onappletviewer
(You can also retrive everything fromhttp://www.cs.indiana.edu/ classes/a348/t540/spr2002/lectures/code/Two/BufferedGraphicsTest.html
/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
To get directly to my applet please click here (IE on Win32 only.)http://www.cs.indiana.edu/ classes/ a348/ t540/ spr2002/ lectures/ code/ Two/ VectorTest.html
A better bet is to simply run your
onappletviewer
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/VectorTest.html
To get directly to my applet please click here (IE on Win32 only.)http://www.cs.indiana.edu/ classes/ a348/ t540/ spr2002/ lectures/ code/ Two/ ActorTest.html
A better bet is to simply run your
onappletviewer
Now a summary of what's needed (in terms of files, listed below):http://www.cs.indiana.edu/ classes/a348/t540/spr2002/lectures/code/Two/ActorTest.html
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.
You also need this interface.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
We cannot create actors just yet. Why? (For at least two reasons).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
The following class provides support for the Actor2D
class.
Then a simple test, to prove the concept.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
And here's an HTML file in case you want to save some typing.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
Now we start providing the missing classes and build an example.<html> <head> <title>VectorTest</title> </head> <body> <hr> <applet code=VectorTest.class width=300 height=300></applet> <hr> </body> </html>
Here's the file ImageLoader.java
Next, we provide the fileimport 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
ImageGroup.java
Here's fileimport 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
ActorGroup2D.java
Here'simport 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
AnimationStrip.java
Here'simport 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
Animator.java
Here'simport 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
Robot.java
Here'simport 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
RobotGroup.java
Now a number ofimport 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
.gif
files may be needed:
Here's file ActorTest.java
(contains RobotAdapter
)
Here'simport 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
ActorTest.html
now:
You should already have<html> <head> <title>ActorTest</title> </head> <body> <hr> <applet code=ActorTest.class width=300 height=300></applet> <hr> </body> </html>
BufferedGraphics.java
but here it is anyway:
You also should already haveimport 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
BufferedGraphicsTest.java
but here it is anyway:
Here's pipe.gif.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
We should also note StaticActor.java
We should also make a note ofimport 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
StaticActorGroup.java
now:
That's basically it for this chapter (10, animport 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
Actor2D
class).
T540