Question

I am trying to develop a game that imports the background images from a [100][100] matrix. The matrix will hold int values to correlate to what should be drawn on the background. A loop draws the images to the canvas and updates it based on key input from the user. Everything paints and moves fine however, it is very slow. Is there a better way to load the images rather than the way I am doing it?

This is the main game class:

package com.game.src.main;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;

public class Game extends Canvas implements Runnable{

static GraphicsEnvironment environment;
static GraphicsDevice device;
private static final long serialVersionUID = 1L;
public static final int WIDTH = 320;
public static final int HEIGHT = WIDTH / 12 * 9;
public static final int SCALE = 2;
public static final String TITLE = "fgfdsa";
private boolean running = false;
private Thread thread;

private Player p;
private Background b;
private Controller c;
private BufferedImage spriteSheet;

boolean isFiring = false;

public void init(){

    BufferedImageLoader loader = new BufferedImageLoader();
    try{
        spriteSheet = loader.loadImage("/sprite_sheet_test.png");

    }catch(IOException e){
        e.printStackTrace();
    }
    requestFocus();
    addKeyListener(new KeyInput(this));
    c = new Controller();
    p = new Player(getWidth() / 2, getHeight() / 2, this);
    b = new Background(this);
}
private synchronized void start(){

    if(running)
        return;
    running = true;
    thread = new Thread(this);
    thread.start();
}
private synchronized void stop(){
    if(!running)
        return;
    running = false;
    try {
        thread.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.exit(1);
}

public void run(){
    init();
    long lastTime = System.nanoTime();
    final double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks;
    double delta = 0;

    int updates = 0;
    int frames = 0;
    long timer = System.currentTimeMillis();

    while(running){
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;

        if(delta >= 1){
            tick();
            updates++;

            delta--;
        }
        render();
        frames++;

        if(System.currentTimeMillis() - timer > 1000){
            timer += 1000;
            System.out.println(updates + " Ticks, Fps " + frames);
            updates = 0;
            frames = 0;
        }       
    }
    stop();
}
public void tick(){
    p.tick();
    b.tick();
    c.tick();
}
public void render(){
    BufferStrategy bs = this.getBufferStrategy();
    if(bs == null){
        createBufferStrategy(3);
        return;
    }
    Graphics g = bs.getDrawGraphics();

    b.render(g);
    p.render(g);
    c.render(g);

    g.dispose();
    bs.show();      
}
public void keyPressed(KeyEvent e){ 
    int key = e.getKeyCode();

    switch(key){
    case 37:
        b.setX(5);
        break;
    case 38:
        b.setY(5);
        break;
    case 39:
        b.setX(-5);
        break;
    case 40:
        b.setY(-5);
        break;
    case 32:
        if(!isFiring){
        c.addBullet(new Bullet(p.getX(), p.getY(), this));
        isFiring = true;
        }
    }
}
public void keyReleased(KeyEvent e){
    int key = e.getKeyCode();
    switch(key){
    case 37:
        b.setX(0);
        break;
    case 38:
        b.setY(0);
        break;
    case 39:
        b.setX(0);
        break;
    case 40:
        b.setY(0);
        break;
    case 32:
        isFiring = false;
    }
}
public static void main(String[] args){
    Game game = new Game();
    game.setPreferredSize(new Dimension(600, 600));
    game.setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
    game.setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));

    JFrame frame = new JFrame(game.TITLE);
    frame.add(game);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
    device = environment.getDefaultScreenDevice();
    frame.setExtendedState(JFrame.MAXIMIZED_BOTH);

    game.start();

}   
public BufferedImage getSpriteSheet(){
    return spriteSheet;
}
}

This is the background class used to draw the image to the screen:

package com.game.src.main;


import java.awt.Graphics;
import java.awt.image.BufferedImage;


public class Background {

private BufferedImage grass;
private BufferedImage background;
private BufferedImage tree;

int[][] matrix;

Game game;

//original starting coordinates of matrix to be drawn
int setX = -3200;
int setY = -3200;

//integers used to update coordinates of the matrix to be drawn
int helpX = 0;
int helpY = 0;

public Background(Game game){
    this.game = game;

    // load matrix into matrix array
    GetMatrix gm = new GetMatrix();
    matrix = gm.getMatrix();
        //import the sprite from game class
        background = game.getSpriteSheet();

    //call sprite sheet class
    SpriteSheet ss = new SpriteSheet(background);
    //get coordinates of grass image
    grass = ss.grabImage(1, 1, 32, 32);
    // get coordinates of tree image
    tree = ss.grabImage(4, 1, 32, 32);
}
public void tick(){
    //update the start pixel of the background
    setX += helpX;
    setY += helpY;
    if(setX > 0)
        setX = 0;
    if(setX < -4500)
        setX = -4500;
    if(setY > 0)
        setY = 0;
    if(setY < -5340)
        setY = -5340;
}

public void render(Graphics g){
    int x = 0;
    int y = 0;

    for(int i = setX; i < setX + 6400; i +=64){
        x = 0;
        for(int j = setY; j < setY + 6400; j += 64){

            switch(matrix[x][y]){
            case 0: g.drawImage(grass, i, j, i + 64, j + 64,
                    0, 0, 32, 32, null);
                    break;
            case 1:
                g.drawImage(grass, i, j, i + 64, j + 64,
                        0, 0, 32, 32, null);
                g.drawImage(tree, i, j, i + 64, j + 64,
                    0, 0, 32, 32, null);    
            }
            x++;
        }
        y++;
    }   
}

//sets the background start coordinates from key input
public void setX(int x){
    helpX = x;
}
public void setY(int y){
    helpY = y;
}
}
Was it helpful?

Solution

It is not obvious what SpriteSheet#grabImage(...) does. But I'm pretty sure that there is some call to BufferedImage#getSubImage(...) involved. Is that right?

If this is right, there are two potential issues here:

  1. When you are loading an image with ImageIO, the type of the resulting image is not known. By "type" I refer to the BufferedImage#getType(). This type may be BufferedImage.TYPE_CUSTOM, particularly when it is a PNG with transparency. When an image with this type is painted, then this painting is awfully slow, because there are some color conversions done internally.

  2. When you call BufferedImage#getSubImage(...), then the image on which you call this method will become "unmanaged". That means that the actual image data can no longer be held directly in video memory. (There are some very involved technical details behind that one. And these details may change between different JRE versions. For example, they changed between Java 6 and Java 7. However, the rule of thumb is: If you want to draw images with high performance, don't call BufferedImage#getSubImage(...) on the image that you want to paint)

The solution for both issues can be to convert the images into managed images of the type BufferedImage.TYPE_INT_ARGB. So for each image that you want to paint, you can call

BufferedImage toPaint = convertToARGB(originalImage);

with this method:

public static BufferedImage convertToARGB(BufferedImage image)
{
    BufferedImage newImage = new BufferedImage(
        image.getWidth(), image.getHeight(),
        BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = newImage.createGraphics();
    g.drawImage(image, 0, 0, null);
    g.dispose();
    return newImage;
}

In your example, you could apply this to your grass and tree images.

Another (maybe even more important) issue is that you seem to be drawing your tiles scaled: You seem to paint a 64x64 pixels sprite with a size of 32x32. If this is correct, then you could consider rescaling the input image once, and then drawing the tiles with their original size of 32x32.

In any case, it's hard to predict how much speedup each of these changes will actually bring, but they should be worth a try.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top