1
\$\begingroup\$

I'm trying to come up with my own game using Java AWT after watching a few video tutorials. However, I encountered a problem where I cannot draw an external image file that I loaded using the BufferedImage object.

The problem seems to be on the method that I'm using to draw the image on the screen, where I'm using Graphics2D.drawImage() method to draw.


Here is part of my code (I modified and skipped parts that seemed irrelevant to the topic):

Window Class

public class Window extends JPanel{
    public Window(int width, int height) {
        JFrame frame = new JFrame("Zerro's Game");
        
        frame.setPreferredSize(new Dimension(width, height));
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        frame.pack();
    }
}

Game Class

public class Game extends JFrame implements Runnable {
    // Dimension for Main Frame
    private static final int WIDTH = 640;
    private static final int HEIGHT = 480;

    // Image
    private BufferedImage image;
    private Graphics2D g;

    public void run() {
        this.requestFocus();
        
        // For rendering purposes
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        g = (Graphics2D) image.getGraphics();
        
        //Game Loop
        long now;
        long updateTime;
        long wait;

        final int TARGET_FPS = 60;
        final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
        
        while (isRunning) {
            now = System.nanoTime();
            
            update();
            render();
            
            updateTime = now - System.nanoTime();
            wait = (OPTIMAL_TIME - updateTime) / 1000000;
                    
            try {
                Thread.sleep(wait);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void render() {
        if(gameState == STATE.GAME) {
            handler.render(g);
        } else if(gameState == STATE.MENU) {
            menu.render(g);
        }
    }

Menu Class

public class Menu extends KeyAdapter{
    private BufferedImage image;

    public void render(Graphics2D g) {
        try {
            image = ImageIO.read(getClass().getResource("/Pixel_Background.png"));
            g.drawImage(image, 0, 0, null);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

This code results in making empty frame without any content inside. I confirmed that it properly loads an image by using System.out.println(image.getHeight()) which prints out the exact height of an image that I'm using.

I've seen some comments in the internet that I need to use paintComponent() method. However, I'm just wondering if it's not a good idea to use paintComponent() in game development as the video tutorials (3 of them) that I watched didn't use such method to draw images.

Also, I'm wondering about the reason why we pass in the Graphics2D object as parameter in all the render() methods.

\$\endgroup\$

3 Answers 3

0
\$\begingroup\$

You never actually draw the image you draw the loaded image on onto the screen. The data won't get magically transferred to the screen. That's why people use the paintComponent method, it provides you with the graphics component of the panel.

Also, when you're learning a language, it's not the best idea to jump into gamedev with it, since most tutorials won't teach you the basics you need to know (like OO programming). Some background knowledge would answer your question about passing the graphics object to other methods. If you don't pass it, then you simply can't use it.

\$\endgroup\$
0
0
\$\begingroup\$

You may need to add the game to the JFrame. New code: Window.java:

public class Window extends JPanel{
    public Window(int width, int height, Game game) {
        JFrame frame = new JFrame("Zerro's Game");

        frame.setPreferredSize(new Dimension(width, height));

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.pack();
        frame.add(game);
        frame.setVisible(true);
    }
}

then just add the word "this" as an input (not a string just the word without quotes) to wherever you're making the window

\$\endgroup\$
0
\$\begingroup\$

Both existing answers point us to the solution because

  1. You are rendering to BufferedImage you created. That does not automatically show on screen, it is just image in memory. As @Bálint pointed out
  2. Even if you used graphics of the Game you are not showing this JFrame, instead creating new JFrame in Window class. As @BigmancozmoPlayz pointed out

Since Game class is already JFrame, we do not need to create a new JFrame. For simplicity I also removed Window (since JFrame can be shown by itself). I also called Game.run() method.

public class GameLauncher {

    public static final int WIDTH = 640;
    public static final int HEIGHT = 480;
    
    public static void main(String[] args) throws HeadlessException, IOException {
        Game frame = new Game();
        frame.setTitle("Zerro's Game");
        frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        frame.pack();
        frame.run();
    }
}

Then to the rendering part. We do not know when window is really painted to screen (it would be waste of resources to paint UI all the time) so we need to ask for it to be repainted and do our painting in overriden paint method:

private BufferedImage buffer = new BufferedImage(GameLauncher.WIDTH, GameLauncher.HEIGHT, BufferedImage.TYPE_INT_RGB);
private Graphics bufferGraphics = buffer.getGraphics();

...

update();
render();
repaint();

...

@Override
public void paint(Graphics g) {
    g.drawImage(buffer, 0, 0, null);
}

Here we are using buffer image because you want to separate painting from showing it and I believe your tutorials meant to do so. (Reaslistically you need at least two buffers/images, one where result is being prepared and one with result ready to be shown, to prevent showing incomplete rendering result)

And now we need to render what we want to buffer (it is wise for image to be already loaded):

private void render(){
    bufferGraphics.drawImage(image, 0, 0, null);
    bufferGraphics.drawString(System.currentTimeMillis() + " render!", 10, GameLauncher.HEIGHT - 20);
}

With this, image is shown plus nice timer showing we are updating buffer.

My rendered image

Here is whole Game class to have runnable example (with GameLanucher class):

public class Game extends JFrame implements Runnable {
    private static final long serialVersionUID = 1L;
    // Dimension for Main Frame
    private boolean isRunning = true;
    // Image
    private BufferedImage image = ImageIO.read(getClass().getResource("/Pixel_Background.png"));
    private BufferedImage buffer = new BufferedImage(GameLauncher.WIDTH, GameLauncher.HEIGHT, BufferedImage.TYPE_INT_RGB);
    private Graphics bufferGraphics = buffer.getGraphics();

    public Game() throws HeadlessException, IOException {
        
    }

    @Override
    public void run() {
        this.requestFocus();
        
        // Game Loop
        long now;
        long updateTime;
        long wait;

        final int TARGET_FPS = 60;
        final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;

        while (isRunning) {
            now = System.nanoTime();

            update();
            render();
            repaint();

            updateTime = now - System.nanoTime();
            wait = (OPTIMAL_TIME - updateTime) / 1000000;

            try {
                Thread.sleep(wait);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void update() {
        // TODO Auto-generated method stub
    }
    
    @Override
    public void paint(Graphics g) {
        g.drawImage(buffer, 0, 0, null);
    }

    private void render(){
        bufferGraphics.drawImage(image, 0, 0, null);
        bufferGraphics.drawString(System.currentTimeMillis() + " render!", 10, GameLauncher.HEIGHT - 20);
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .