0

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;
}
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
seiko149
  • 19
  • 6
  • So is the painting slow or the loading of the images from disk slow? – Charlie Mar 06 '14 at 17:03
  • 1
    Holy unsigned long batman! Please refer to http://www.sscce.org/ for how to making `Short, Self Contained, Correct (Compilable), Examples`! – theGreenCabbage Mar 06 '14 at 17:03
  • I believe its the loading of the images. When I bypass the images and print filled rectangles my ticks are staying at 60 and my fps drops to around 350. When I don't paint anything my ticks are at 60 and my fps is at 2500. When I paint the images from the file loaded My ticks drop to around 12 and my fps drops to around 15 – seiko149 Mar 06 '14 at 17:10
  • haha ill keep that in mind I always thought the more code the better so you could compile it easily. I will fix it in the future. – seiko149 Mar 06 '14 at 17:11
  • Maybe the double for loop in the render method – polypiel Mar 06 '14 at 17:20

1 Answers1

4

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.

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • That is exactly how I have it set up. SpriteSheet uses a method to return a part of the sprite sheet and then paint it on the screen. I can easily bypass that by writing the coordinates directly in the loop to paint the background. However, when I try to convert the buffered image I loaded using TYPE_INT_RGB the whole screen just turns black. – seiko149 Mar 06 '14 at 17:46
  • So it does not work any more when you replace `grass = ss.grabImage(1, 1, 32, 32);` with `grass = convertToARGB(ss.grabImage(1, 1, 32, 32));`? That's hard to imagine, can you provide more infos (maybe an example, together with an image, where this behavior can be reproduced)? – Marco13 Mar 06 '14 at 17:50
  • haha my fault I was testing it and forgot to put my loop back into draw it.. It is working however it is still very slow. However, a little faster then before – seiko149 Mar 06 '14 at 17:52
  • Ahhh I took of the scaling and painted them directly according to their size. It is a lot faster now. You sir are a genius. Thank you for your help – seiko149 Mar 06 '14 at 17:55