Joss
Joss

Reputation: 135

Java animation and the Repaint Manager

Just hoping people can help me understand properly how the RepaintManager works when trying to create animations. Basically I am creating a program that draws and updates creatures/images to a JFrame. Each creature object contains all the information for paint to be able to draw it such as x,y coordinates and an BufferedImage. Currently every time a creature object moves it calls repaint(), which as you will see below in my paint/paintComponent (not sure which is is best) method will run a loop through all existing creatures and update their position & graphics on the screen.

So my query, is this the best way to do this as I am concerned that the RepaintManager is redrawing everything on the screen, which is not very efficient. I have a read a few posts/articles about clipping or overriding update methods but just cant quite get my head around them - so is it possible to every time repaint is called it only updates objects/creatures that have changed?

Some code of what I am talking about:

public void paintComponent (Graphics g) {
       super.paintComponent(g);
       //setDoubleBuffered(true); //Probably not needed?
       Graphics2D g2d = (Graphics2D) g;

       //Draws the land area for penguins.
       g2d.setColor(new Color(121,133,60));
       g2d.fill3DRect(land.x, land.y, land.width, land.height, true);
       //OR g2d.fill(land);
       g2d.setColor(Color.BLUE);
       g2d.fill(sea);

       drawCreatures(g2d);              
    }

All graphics in the paint method won't change and shouldnt need to be repainted, however the method below will...

   private void drawCreatures(Graphics2D g2d) {

   try {
        for (Creature c : crlist) {
            g2d.drawImage(c.createImage(txs,tys), c.getPos(1).width, c.getPos(1).height, this);
            //g2d.drawImage(cImg,c.getPos(1).width,c.getPos(1).height,this);

            if (c instanceof Fish && c.getMating().equals(Mating.WBABY)) {
                int xos=0;
                for (Creature fb : c.getChildren()) {
                    if (fb.getMating().equals(Mating.BABY)) g2d.drawImage(fb.createImage(txs,tys),c.getPos(1).width+xos,c.getPos(1).height+50,txs/2,tys/2,this);
                    xos+=25;
                }
            }

            if (c.getInfo()==true) displayInfo(g2d,c); //This changes when a creature is clicked on.
            else if (c.getState()!=State.NORMAL || c.getMating().equals(Mating.LOVE)) drawState(g2d,c); //updated through a creature's move/search methods.
       }
   }
   catch(ConcurrentModificationException cme) { System.out.println("CME ERROR!"); }

}

The important part is the first line of the enhanced for loop as this draws images to the JFrame window. The createImage method simply gets the creatures image and converts/modifies e.g. flips it when they move left before paint uses it.

Animation is currently handled by each creature running a Thread, however I am also wondering if a Swing Timer would be better as I am currently getting ConcurrentModificationException when a creature tries to add a new creature to crlist.

private class Creature implements Behaviours<Creature>, Runnable {
//...variables
//...constructor
//...methods for updating the creature/object

@Override
public void run() {

    while (getState()!=State.DEAD) {
        move(); //where the coordinates of a creature is updated.
        repaint();
        try { Thread.sleep(1000); }
        catch(InterruptedException e) {}
    }
}

So every 1 sec for each creature move and repaint is called, but I would like a way either not have to loop through each creature in paint or ensure when repaint is called it only updates the creature that called it.

Any suggestions or just pointing me to another post would be appreciated. Thanks.

Upvotes: 0

Views: 338

Answers (1)

Adem
Adem

Reputation: 9429

you can use this code. you have to extends your drawing objects to PaintObject, background as well. and implement their paint in their own. I hope this code can give you idea. you can collect all area needs to be repaint. then, find which object are in there. after, repaint only this area

class GameCanvas extends Canvas {
    private static final int FPS = 24;
    private boolean isAlive;
    private Vector<PaintObject> allObjects = new Vector<Rec.PaintObject>();
    private Vector<Rectangle> paintingClipsInTurn = new Vector<Rectangle>();
    public GameCanvas() {
        isAlive = true;
        new Thread(){
            @Override
            public void run() {
                while(isAlive){
                    long ti1 = System.currentTimeMillis();
                    repaint();
                    long ti2 = System.currentTimeMillis();
                    long frameTime = ti2-ti1;
                    long targetFrameTime = 1000/FPS;
                    if (targetFrameTime > frameTime){
                        try {
                            Thread.sleep(targetFrameTime - frameTime);
                        } catch (InterruptedException e){
                        }
                    }
                }
            }
        }.start();
    }

    @Override
    public void paint(Graphics g) {
        if (paintingClipsInTurn.size() > 0) {
            Rectangle s = paintingClipsInTurn.get(0);
            int min_x = s.x;
            int min_y = s.y;
            int max_x = s.x + s.width;
            int max_y = s.y + s.height;
            for (Rectangle r : paintingClipsInTurn) {
                if (r.x < min_x)
                    min_x = r.x;
                if (r.y < min_y)
                    min_y = r.y;
                if (r.x + r.width > max_x)
                    max_x = r.x + r.width;
                if (r.y + r.height > max_y)
                    max_y = r.y + r.height;
            }
            Rectangle totalclip = new Rectangle(min_x, min_y, max_x-min_x, max_y-min_y);
            for (PaintObject object : allObjects) {
                Rectangle r1 = object.getClip();
                if (totalclip.intersects(r1)){
                    object.paintObject(g);
                }
            }
        }
        paintingClipsInTurn.clear();
    }

    public void addDrawComponent(PaintObject object){
        object.parent = this;
        allObjects.add(object);
    }
    public void removeDrawComponent(PaintObject object){
        object.parent = null;
        allObjects.remove(object);
    }
    void requestRepaint(PaintObject object){
        paintingClipsInTurn.add(object.getClip());
    }
}


abstract class PaintObject {

    GameCanvas parent;
    private int x,y,w,h;
    public void setPosition(int x,int y,int w,int h){
        if (parent != null) parent.requestRepaint(this);
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        if (parent != null) parent.requestRepaint(this);
    }
    Rectangle getClip(){
        Rectangle rect = new Rectangle();
        rect.x = x;
        rect.y = y;
        rect.width = w;
        rect.height = h;
        return rect;
    }
    void paintObject(Graphics g){
        g.translate(x, y);
        Shape oldClip = g.getClip();
        g.setClip(0, 0, w, h); // in here you have to actually find intersection of current clip and object clip. this code needs to be fixed. 
        paint(g);
        g.setClip(oldClip);
        g.translate(-x, -y);
    }
    public abstract void paint(Graphics g);
}

Upvotes: 0

Related Questions