0

I want to have an image on a screen to simply move around. I'm able to get it to show up on the screen, but I can't get it to move. Here's some code:

Player:

public class Player {

double positionX;
double positionY;
int destinationX;//Used when moving from place to place
int destinationY;
Tool currentTool;
int direction; //Position the image is facing
int dx;
int dy;
private String girl = "girl.png";
ImageIcon ii = new ImageIcon(this.getClass().getResource(girl));
private Image image = ii.getImage();
private boolean visible = true;

Image playerImage;

public Player(){
    positionX=30;
    positionY=20;
    dx = 2;
    dy = 2;
    destinationX=(int)positionX;
    destinationY=(int)positionY;

    //this.playerImage=playerImage;
}

public void doAction() {
    //currentTool.getNum();
}

public boolean isVisible() {
    return visible;
}

public void setVisible(Boolean visible) {
    this.visible = visible;
}

public Image getImage() {
    return image;
}

public void move(){
    //MOVE LEFT AND RIGHT
    if(destinationX<positionX){
        positionX-=dx;
    }
    if(destinationX>positionX){
        positionX+=dx;
    }

    //MOVE UP AND DOWN
    if(destinationY<positionY){
        positionY-=dy;
    }
    if(destinationY>positionY){
        positionY+=dy;
    }
}

public void setDestination(int x, int y){
    positionX=x;
    positionY=y;
    System.out.println(x + "," + y);
}

public void draw(Graphics g,ImageObserver io){
    g.drawImage(image, (int)positionX,(int) positionY,io);
}

Board (implements game elements/components):

public class Board extends JPanel implements Runnable {

private static final int NO_DELAYS_PER_YIELD = 16;
/* Number of frames with a delay of 0 ms before the
   animation thread yields to other running threads. */

private static int MAX_FRAME_SKIPS = 5;
// no. of frames that can be skipped in any one animation loop
// i.e the games state is updated but not rendered


private Thread animator;
int x, y;
final int frameCount = 8;
BufferedImage flowers;
private int[][] fPos = {{232, 15},{400, 200},{335, 335}}; // flower coordinates 
private static int bWIDTH = 500; // width of window 
private static int bHEIGHT = 400;// height of window
private Font font;
private FontMetrics metrics;
private House house = new House();
private Flower flower = new Flower(); 
private Player girlP = new Player();
private int px = 200;
private int py = 400;



private long period;

private volatile boolean running = false;
private volatile boolean gameOver = false;
private volatile boolean isPaused = false;

private Graphics dbg;
private Image dbImage = null;


public Board(long period) {

    this.period = period;

    setBackground(Color.white);
    setPreferredSize(new Dimension(bWIDTH, bHEIGHT));

    setFocusable(true);
    requestFocus();     //JPanel now receives key events
    readyForTermination();

    // create game components

    // listen for mouse presses
    addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
            testPress(e.getX(), e.getY());
        }
    }); 

    // set up message font
    font = new Font("SansSerif", Font.BOLD, 24);
    metrics = this.getFontMetrics(font);

    x = 15;
    y = 150;
}   // end of 'Board()'

public void addNotify() {
    super.addNotify();
    startGame();
}

public void startGame() {
    if (animator == null || !running) {
        animator = new Thread(this);
        animator.start();
    }
}

public void pauseGame() {
    isPaused = true;
}

public void resumeGame() {
    isPaused = false;
}

public void stopGame() {
    running = false;
}

private void readyForTermination() {
    addKeyListener( new KeyAdapter() {
        public void keyPressed(KeyEvent e) {

            // listen for escape, q, or ctrl-c
            int keyCode = e.getKeyCode();
            if ((keyCode == KeyEvent.VK_ESCAPE) ||
                    (keyCode == KeyEvent.VK_Q) ||
                    (keyCode == KeyEvent.VK_END) ||
                    ((keyCode == KeyEvent.VK_C) && e.isControlDown()) ){
                running = false;
            }
        }
    });
}

private void testPress(int x, int y) {
    if (!isPaused && !gameOver) {
        // do something..
        px = x;
        py = y;
        System.out.println(px + ", " + py);
    }
}

private void gameRender() {
    if (dbImage == null) { // creating the buffer
        //dbImage = createImage(bWIDTH, bHEIGHT);
        if (dbImage == null) {
            //System.out.println("dbImage is null");
            return;
        }
        else 
            dbg = dbImage.getGraphics();
    }

    // clearing the background
    dbg.setColor(Color.gray);
    //dbg.fillRect(0, 0, bWIDTH, bHEIGHT);

    dbg.setColor(Color.blue);
    dbg.setFont(font);


    //drawing game elements....

    if (gameOver) {
        gameOverMessage(dbg);
    } // end of gameRender()
}

private void gameUpdate() {
    if (!isPaused && !gameOver) {
        //girlP.move();
    }
}

private void gameOverMessage(Graphics g)
// center the game-over message
{ // code to calculate x and y...
    String msg = "Game Over";
    g.drawString(msg, x, y);
}  // end of gameOverMessage( )


public void paint(Graphics g) {
    super.paint(g);

    if (dbImage != null) {
        g.drawImage(dbImage, 0, 0, null);   
    }

//      if (house.isVisible()) {
//          g.drawImage(house.getImage(), 10, 10, this);
//      }


    if (flower.isVisible()) {
        for (int i = 0; i < 3; i++) {
            g.drawImage(flower.getImage(), fPos[i][0], fPos[i][1],this);
        }
    }

    girlP.draw(g, this);


    //girlP.setDestination(0, 0);
    //girlP.move();

//      int red = 103;
//      int green = 10;
//      int blue = 100;
//      Color square = new Color(red, green, blue);
//      g.fillRect(x, y, sqW, sqH);


    //Toolkit.getDefaultToolkit().sync();
}

private void paintScreen() {
    // actively render the buffer to the screen

    Graphics g;
    try {
        g = this.getGraphics(); // get the panel's graphic context
        if ((g != null) && (dbImage != null))
            g.drawImage(dbImage, 0, 0, null);
        Toolkit.getDefaultToolkit().sync(); // sync the display on some systems
        g.dispose();
    }catch (Exception e) {
        System.out.println("Graphics context error: " + e);
    }
}


public void run() {
    // repeatedly update, render, and sleep

    long beforeTime, afterTime, timeDiff, sleep;
    long overSleepTime = 0L;
    int noDelays = 0;
    long excess = 0L;


    beforeTime = System.nanoTime();

    running = true;
    while (running) {
        gameUpdate();
        gameRender();
        paintScreen(); // this will draw buffer to screen instead of repaint()
        //updateChange();
        girlP.move();

        afterTime = System.nanoTime();
        timeDiff = afterTime - beforeTime;
        sleep = (period - timeDiff) - overSleepTime; // time left in this loop

        if (sleep > 0) { // some time left in this cycle

            try {
                Thread.sleep(sleep/1000000L); // nano -> ms
            }catch (InterruptedException e) {
                System.out.println("Interrupted");
            }   
            overSleepTime = (System.nanoTime() - afterTime) - sleep;


        }
        else {      // sleep <= 0; frame took longer than the delay
            excess -= sleep; // store excess time value
            overSleepTime = 0L;

            if (++noDelays >= NO_DELAYS_PER_YIELD) {
                Thread.yield( );   // give another thread a chance to run
                noDelays = 0;
            }
        }

        beforeTime = System.nanoTime();

        /* If frame animation is taking too long, update the game state
           without rendering it, to get the updates/sec nearer to
           the required FPS. */
        int skips = 0;
        while((excess > period) && (skips < MAX_FRAME_SKIPS)) {
            excess -= period;
            gameUpdate();      // update state but don't render
            skips++;
        }

    }
    System.exit(0);
} // end of run();

}

This is what I'm getting so far

Beau Grantham
  • 3,339
  • 5
  • 29
  • 43
blutuu
  • 191
  • 1
  • 11
  • Don't use getGraphics from UI components, this is not how painting should be done. Swing uses a passive rendering method and all painting should be done within the context of an appropriate paint method. Updating your game state within a thread is also dangerous, as a repaint may occur at any time, meaning you could up end up with dirty/inconsistent paints. You need to isolate the game model from the screen pain routines, possibly using a series of backing buffers to render the state, switching them as required. – MadProgrammer Apr 06 '13 at 23:01
  • Any suggestions on how to fix that? i.e. an appropriate paint method. – blutuu Apr 06 '13 at 23:26
  • Plenty, but I have to goto training and shopping so I don't have the time right now - sorry – MadProgrammer Apr 06 '13 at 23:28
  • You could take a quick look at [this](http://stackoverflow.com/questions/13540534/how-to-make-line-animation-smoother/13547895#13547895) and [this](http://stackoverflow.com/questions/14886232/swing-animation-running-extremely-slow/14902184#14902184) and [this](http://stackoverflow.com/questions/13022754/java-bouncing-ball/13022788#13022788) and [this](http://stackoverflow.com/questions/14593678/multiple-bouncing-balls-thread-issue/14593761#14593761) and [this](http://stackoverflow.com/questions/12642852/the-images-are-not-loading/12648265#12648265) as some examples – MadProgrammer Apr 06 '13 at 23:31
  • @MadProgrammer Ok thanks. I will see what I can do with those sources – blutuu Apr 06 '13 at 23:33
  • @MadProgrammer Another thing. Without changing my engine too much could you suggest a way to accomplish what I'm trying to do? At this point this is going to be used to prototype my game on Monday. I just need to very, very simple implementation of the project. Let me know. – blutuu Apr 07 '13 at 00:04

1 Answers1

2

There are a lot of things that stand out for me.

The use of getGraphics is a very bad idea. Swing uses a passive rendering engine that is controlled by the RepaintManager. This basically means that while you can request a repaint, you have no control over when that update may occur.

Swings paint process is also stateless. That means that what ever was previously painted will be thrown away and anything you want to be painted must be re-applied within one of the paint methods.

Your movement code was wrong. You were assigning the values directly to the position rather then the destination variables. I modified the movement code to do nothing if no movement was required, I actually did this for debug purposes, but hay.

Your core rendering engine is ... weird ...

It's unlikely that you're ever going to need more the about 25 fps or equivalent, so using nanoseconds is probably a little excessive, millisecond accuracy is probably more then enough. When I changed the delay engine, I got a much better result.

I also changed the core rendering process. The engine now uses a page flipping process. It uses two BufferedImages, one is the activeBuffer which is painted to the screen, the other is a scratchBuffer which is used to update the game state. I then flip then as required. This means that what is been painted on the screen is effected by what is been rendered by the engine.

There's probably a dozen other things, but you can compare the differences yourself ;)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.tools.Tool;

public class AnimationEngine {

    public static void main(String[] args) {
        new AnimationEngine();
    }

    public AnimationEngine() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new Board(40));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class Board extends JPanel implements Runnable {

        protected static final Object UPDATE = new Object();
        private static final int NO_DELAYS_PER_YIELD = 16;
        /* Number of frames with a delay of 0 ms before the
         animation thread yields to other running threads. */
        private static int MAX_FRAME_SKIPS = 5;
// no. of frames that can be skipped in any one animation loop
// i.e the games state is updated but not rendered
        private Thread animator;
        int x, y;
        final int frameCount = 8;
        BufferedImage flowers;
        private int[][] fPos = {{232, 15}, {400, 200}, {335, 335}}; // flower coordinates 
        private static int bWIDTH = 500; // width of window 
        private static int bHEIGHT = 400;// height of window
        private Font font;
        private FontMetrics metrics;
//        private House house = new House();
//        private Flower flower = new Flower();
        private Player girlP = new Player();
        private int px = 200;
        private int py = 400;
        private long period;
        private volatile boolean running = false;
        private volatile boolean gameOver = false;
        private volatile boolean isPaused = false;
//        private Graphics dbg;
        private BufferedImage activeBuffer = null;
        private BufferedImage scratchBuffer = null;

        public Board(long period) {

            this.period = period;

            setBackground(Color.white);
            setPreferredSize(new Dimension(bWIDTH, bHEIGHT));

            setFocusable(true);
            requestFocus();     //JPanel now receives key events
            readyForTermination();

            // create game components

            // listen for mouse presses
            addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    System.out.println("Clicked");
                    girlP.setDestination(e.getX(), e.getY());
                }
            });

            // set up message font
            font = new Font("SansSerif", Font.BOLD, 24);
            metrics = this.getFontMetrics(font);

            x = 15;
            y = 150;
        }   // end of 'Board()'

        public void addNotify() {
            super.addNotify();
            startGame();
        }

        public void startGame() {
            if (animator == null || !running) {
                animator = new Thread(this);
                animator.start();
            }
        }

        public void pauseGame() {
            isPaused = true;
        }

        public void resumeGame() {
            isPaused = false;
        }

        public void stopGame() {
            running = false;
        }

        private void readyForTermination() {
            addKeyListener(new KeyAdapter() {
                public void keyPressed(KeyEvent e) {

                    // listen for escape, q, or ctrl-c
                    int keyCode = e.getKeyCode();
                    if ((keyCode == KeyEvent.VK_ESCAPE)
                                    || (keyCode == KeyEvent.VK_Q)
                                    || (keyCode == KeyEvent.VK_END)
                                    || ((keyCode == KeyEvent.VK_C) && e.isControlDown())) {
                        running = false;
                    }
                }
            });
        }

        @Override
        public void invalidate() {
            synchronized (UPDATE) {
                activeBuffer = null;
                scratchBuffer = null;
            }
            super.invalidate();
        }

        private void gameRender() {
            synchronized (UPDATE) {
                if (getWidth() > 0 && getHeight() > 0) {
                    if (scratchBuffer == null) {
                        scratchBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                    }

                    Graphics2D dbg = scratchBuffer.createGraphics();

                    // clearing the background
                    dbg.setColor(Color.GRAY);
                    dbg.fillRect(0, 0, scratchBuffer.getWidth(), scratchBuffer.getHeight());

                    dbg.setColor(Color.blue);
                    dbg.setFont(font);


                    //drawing game elements....
                    girlP.draw(dbg, this);

                    if (gameOver) {
                        gameOverMessage(dbg);
                    } // end of gameRender()
                    dbg.dispose();
                }

                BufferedImage tmp = activeBuffer;
                activeBuffer = scratchBuffer;
                scratchBuffer = tmp;
            }
        }

        private void gameUpdate() {
            if (!isPaused && !gameOver) {
                girlP.move();
            }
        }

        private void gameOverMessage(Graphics g) // center the game-over message
        { // code to calculate x and y...
            String msg = "Game Over";
            g.drawString(msg, x, y);
        }  // end of gameOverMessage( )

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (activeBuffer != null) {
                g.drawImage(activeBuffer, 0, 0, null);
            }
        }

        public void run() {

            long beforeTime, afterTime, timeDiff, sleep;
            beforeTime = System.currentTimeMillis();

            running = true;
            while (running) {
                gameUpdate();
                gameRender();

                repaint();

                afterTime = System.currentTimeMillis();
                timeDiff = afterTime - beforeTime;
                sleep = (period - timeDiff); // - overSleepTime; // time left in this loop

                if (sleep > 0) { // some time left in this cycle
                    try {
                        Thread.sleep(sleep); // nano -> ms
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted");
                    }
                } else {      // sleep <= 0; frame took longer than the delay
                    System.out.println("Over...");
                }

                beforeTime = System.currentTimeMillis();
            }
            System.exit(0);
        } // end of run();
    }

    public static class Player {

        double positionX;
        double positionY;
        int destinationX;//Used when moving from place to place
        int destinationY;
        Tool currentTool;
        int direction; //Position the image is facing
        int dx;
        int dy;
        private String girl = "/Player01.png";
        ImageIcon ii = new ImageIcon(this.getClass().getResource(girl));
        private Image image = ii.getImage();
        private boolean visible = true;
        Image playerImage;

        public Player() {
            positionX = 30;
            positionY = 20;
            dx = 4;
            dy = 4;
            destinationX = (int) positionX;
            destinationY = (int) positionY;

            //this.playerImage=playerImage;
        }

        public void doAction() {
            //currentTool.getNum();
        }

        public boolean isVisible() {
            return visible;
        }

        public void setVisible(Boolean visible) {
            this.visible = visible;
        }

        public Image getImage() {
            return image;
        }

        public void move() {
            if (destinationX != positionX || destinationY != positionY) {
                //MOVE LEFT AND RIGHT
                if (destinationX < positionX) {
                    positionX -= dx;
                }
                if (destinationX > positionX) {
                    positionX += dx;
                }

                //MOVE UP AND DOWN
                if (destinationY < positionY) {
                    positionY -= dy;
                }
                if (destinationY > positionY) {
                    positionY += dy;
                }
            }
        }

        public void setDestination(int x, int y) {
            destinationX = x;
            destinationY = y;
        }

        public void draw(Graphics g, ImageObserver io) {
            g.drawImage(image, (int) positionX, (int) positionY, io);
        }
    }
}
MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
  • This looks great! I was going to add an answer to this question as I figured out how o fix my problem, but you hit what I wanted to say and more. I basically moved everything to my gameRender method and uncommented a piece of code I forgot about. It worked after that. When I get some time I will study your code in depth and implement necessary changes. Thanks! – blutuu Apr 07 '13 at 14:07