且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Java游戏图片载入速度很慢

更新时间:2023-02-02 21:40:19

这不是明摆着是什么 SpriteSheet#grabImage(...)一样。但我pretty肯定有一些调用的BufferedImage#getSubImage(...)参与。是这样吗?

如果这是正确的,但这里有两个潜在的问题:


  1. 在您加载与ImageIO的图像,生成的图像的类型是不知道。按型我指的是的BufferedImage#的getType()。这种类型可以是 BufferedImage.TYPE_CUSTOM ,尤其是当它与透明PNG。当这种类型的图像画,那么这幅画是非常缓慢的,因为有在内部做了一些颜色转换。


  2. 当你调用的BufferedImage#getSubImage(...),然后在其上调用此方法将成为非托管的形象。这意味着,在实际图像数据不再能够直接在视频存储器中保持。 。(后面有一个很有些涉及技术细节,而这些细节可能不同版本的JRE之间切换。例如,他们的Java 6和Java 7,但是之间变化,经验法则是:如果你想绘制的图像高性能,不叫的BufferedImage#getSubImage(...),你要画在图像上)


有关这两个问题的解决方案可以将图像转换成键入 BufferedImage.TYPE_INT_ARGB 的管理图像。因此,对于要绘制每个图像,你可以叫

 的BufferedImage toPaint = convertToARGB(originalImage);

用此方法:

 公共静态BufferedImage的convertToARGB(BufferedImage的图像)
{
    BufferedImage的newImage =新的BufferedImage(
        image.getWidth(),image.getHeight(),
        BufferedImage.TYPE_INT_ARGB);
    Graphics2D的G = newImage.createGraphics();
    g.drawImage(图像,0,0,NULL);
    g.dispose();
    返回newImage;
}

在你的榜样,你可以将此你的图像。

另外一个(甚至更重要)的问题是,你似乎在绘画拼贴的缩放的:你似乎描绘出一幅64×64像素,大小32×32的精灵。如果这是正确的,那么你可以考虑重新调整输入图像的一次的,再画与他们原来的32×32大小的瓷砖。

在任何情况下,很难predict多少加速这些变化,实际上带来的,但他们应该是值得一试。

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;
}
}

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.