изображения в java-играх загружаются очень медленно

Я пытаюсь разработать игру, которая импортирует фоновые изображения из матрицы [100][100]. Матрица будет содержать значения int для сопоставления с тем, что должно быть нарисовано на фоне. Цикл рисует изображения на холсте и обновляет его в зависимости от ввода клавиш пользователем. Все рисует и двигается нормально, но очень медленно. Есть ли лучший способ загрузки изображений, а не так, как я это делаю?

Это основной игровой класс:

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

Это фоновый класс, используемый для вывода изображения на экран:

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

person seiko149    schedule 06.03.2014    source источник
comment
Так медленная рисовка или медленная загрузка изображений с диска?   -  person Charlie    schedule 06.03.2014
comment
Святой неподписанный длинный бэтмен! Пожалуйста, обратитесь к sscce.org, чтобы узнать, как сделать Short, Self Contained, Correct (Compilable), Examples!   -  person theGreenCabbage    schedule 06.03.2014
comment
Я считаю, что это загрузка изображений. Когда я обхожу изображения и печатаю заполненные прямоугольники, мои отметки остаются на уровне 60, а частота кадров падает примерно до 350. Когда я ничего не рисую, мои отметки равны 60, а частота кадров составляет 2500. Когда я рисую изображения из файла загружено Мои тики падают примерно до 12, а мой fps падает до 15   -  person seiko149    schedule 06.03.2014
comment
ха-ха, имейте это в виду, я всегда думал, что чем больше кода, тем лучше, чтобы вы могли легко его скомпилировать. Я исправлю это в будущем.   -  person seiko149    schedule 06.03.2014
comment
Возможно, двойной цикл for в методе рендеринга   -  person polypiel    schedule 06.03.2014


Ответы (1)


Не очевидно, что делает SpriteSheet#grabImage(...). Но я почти уверен, что есть какой-то призыв к 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);

с помощью этого метода:

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

В вашем примере вы можете применить это к изображениям grass и tree.

Другая (возможно, даже более важная) проблема заключается в том, что вы, кажется, рисуете свои плитки в масштабе: вы, кажется, рисуете спрайт 64x64 пикселей с размером 32x32. Если это так, то вы можете изменить масштаб входного изображения один раз, а затем отрисовать плитки с исходным размером 32x32.

В любом случае, трудно предсказать, какое ускорение на самом деле принесет каждое из этих изменений, но их стоит попробовать.

person Marco13    schedule 06.03.2014
comment
У меня именно так и настроено. SpriteSheet использует метод для возврата части листа спрайтов, а затем рисует ее на экране. Я могу легко обойти это, написав координаты прямо в цикле, чтобы нарисовать фон. Однако, когда я пытаюсь преобразовать буферизованное изображение, загруженное с использованием TYPE_INT_RGB, весь экран просто становится черным. - person seiko149; 06.03.2014
comment
Значит, при замене grass = ss.grabImage(1, 1, 32, 32); на grass = convertToARGB(ss.grabImage(1, 1, 32, 32)); больше не работает? Это трудно представить, можете ли вы предоставить больше информации (может быть, пример вместе с изображением, где это поведение может быть воспроизведено)? - person Marco13; 06.03.2014
comment
ха-ха, моя вина, я тестировал его и забыл вернуть свою петлю в отрисовку. Он работает, но все еще очень медленно. Однако немного быстрее, чем раньше - person seiko149; 06.03.2014
comment
Аааа я взял масштабирование и нарисовал их прямо в соответствии с их размером. Теперь это намного быстрее. Вы, сэр, гений. спасибо за помощь - person seiko149; 06.03.2014