0

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.

Joss
  • 125
  • 1
  • 7
  • in my opinion, second option, creating threads for each creatures is not nice idea. you eventually will implement first approach. it is common game loop method. some libraries (I dont know which one support swing) lets that you dont care about game loop. you just create object and add to drawing stack. but at background, it does same thing in your first approach. repainting all objects in every frame. it is almost inevitable. – Adem Dec 10 '14 at 16:32
  • and, at some cases, you dont need to draw every creatures. you just need to calculate which area of the screen should update, and which creatures in there (also you need think about background). then clip that area to effect your changes only this clipped area. this is another approach – Adem Dec 10 '14 at 16:35
  • Indeed I may revert back to using a timer for handling creature animation, I am just wanting creatures to be able to move independently an not need to rely on a loop or queue to decide when they move. The clipping suggestion I like though, I am just not sure how to do it - I have seen some do it by overriding update & paintcomponent but those methods don't have access to creature coordinates at the moment, so it wouldn't know what part of the JFrame to update. – Joss Dec 10 '14 at 16:55
  • Also I am wondering if the RepaintManager is clever enough to only repaint graphics that have changed anyway? Just as from what I have read if it was repainting everything each time repaint was called I should notice a flickering effect? – Joss Dec 10 '14 at 17:02
  • I developed some j2me games. we had similar concerns. I will suggest a code for that. do you have always moving background or similar thing ? – Adem Dec 10 '14 at 17:07
  • No currently the background is quite simple and will never change throughout the simulation, therefore it should only need to be painted once (it's the first few lines of code in paint). Basically since repaint is called per creature only that element will have changed, so should be the only graphic that needs updating. However as each creature runs its own timer/thread these repaint calls likely happen at the same time - which may or may not be a good thing :O – Joss Dec 10 '14 at 17:29

1 Answers1

0

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);
}
Adem
  • 9,402
  • 9
  • 43
  • 58
  • Thank you! There is a lot to look through there and understand for that matter as I am still learning Java so haven't used wrapper classes like Vector or created Abstract classes. From what u have described though it sounds promising :) – Joss Dec 11 '14 at 12:01