Подтвердить что ты не робот

Canvas - хорошая практика рендеринга?

В последнее время я использую Canvas для небольших игр Java, и я заметил, что может произойти много странных вещей. Например, ранее сегодня я создал небольшую игру, в которой цель состоит в том, чтобы стрелять в вражеские корабли и получать очки. Игра рисует крошечные 32x32 изображения на экране (иногда немного больше), и по какой-то причине я не обращаю внимания, игра выглядит странно.

Например, у моего корабля есть надпись:

enter image description here

Как вы можете видеть по изображению, текстуры действительно маленькие. Несмотря на это, игра отстает, а иногда вещи выглядят неправильно, например:

enter image description here

Если вы внимательно посмотрите на верхнюю часть панели работоспособности, вы увидите, что она слегка сдвинута вверх, она меня озадачивает, как он происходит, когда весь мой рендеринг буферизуется.

Мой код рендеринга:

public void render(){
    BufferStrategy bs = getBufferStrategy();
    if(bs == null){
        createBufferStrategy(3);
        return;
    }
    Graphics2D g = (Graphics2D)bs.getDrawGraphics();
    toDrawG.setColor(new Color(0x222222));
    toDrawG.fillRect(0, 0, WIDTH, HEIGHT);
    draw((Graphics2D)toDrawG);
    g.drawImage(toDraw, 0, 0, null);
    g.dispose();
    bs.show();
}

public void draw(Graphics2D g){
    if(Settings.planets){
    renderer.renderPlanets();
    }
    if(level != null){
    for(int i = 0 ; i < level.entityList.size(); i++){
        if(level.entityList.get(i) != null){
        level.entityList.get(i).render(renderer);
        }
        }
    }
    renderer.overlayString("Space Game", 20, 20, 24, 0xFFFFFF);
    renderer.overlayString(VERSION, 20, 50, 24, 0xFFFFFF);
    renderer.overlayString("FPS: " + renderer.fps, 20, 70, 24, 0xFFFFFF);
    renderer.overlayString("Ships spawned: " + level.shipsSpawned, 20, 90, 24,     0xFFFFFF);
    renderer.overlayString("Time Survived: " + level.time / 100 + "s", 20, 110,     24, 0xFFFFFF);
    renderer.overlayString("Physics FPS: " + fps, 20, 130, 24, 0xFFFFFF);

    if(currentGui != null){
        currentGui.render(renderer);
    }else{
        map.drawMinimap(SpaceGame.WIDTH-Minimap.WIDTH-20, SpaceGame.HEIGHT-    Minimap.HEIGHT-30);
    }

}

И мой "Render.class", если вам нужно его изучить:

package com.maestrum;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.Random;

public class Render implements Runnable{

public Graphics2D g2d;

public double xScroll,yScroll;

public int frames;
public int fps;
public long lastTime;

public int[] pSequence = new int[40];   
public SpaceGame game;

public Entity trackedEntity;

public Random rand;

public Render(SpaceGame game, Graphics2D g){
    this.game = game;
    this.g2d = g;
    this.rand = new Random();
    for(int i = 0 ; i < 40; i++){
        pSequence[i] = rand.nextInt(15) + 1;
    }
}

@Override
public void run() {     
    renderLoop();
}

public void renderLoop(){
    while(true){
        game.render();
        if(System.currentTimeMillis() - lastTime >= 1000){
            fps = frames;
            frames = 0;
            lastTime = System.currentTimeMillis();
        }
        frames++;
    }
}

public void renderPlanets(){
    overlayImage(ImageHandler.background, 0, 0, 1.5);
    for(int i = 0 ; i < 20; i++){
        overlayImage(ImageHandler.planets[pSequence[i]/4][pSequence[i]%4], i * 400 - xScroll/pSequence[i], i * pSequence[i]*40 - yScroll/pSequence[i]*2, pSequence[i]);
    }
}

private class PlanetRenderer {



}

public void overlayString(String s, double x, double y, int fontSize, int colour){
    drawString(s, x+xScroll, y+yScroll, fontSize, colour);
}

public void overlayRectangle(double x, double y, int xs, int ys, int colour){
    drawRectangle(x+xScroll, y+yScroll, xs, ys, colour);
}

public void overlayBlurred(BufferedImage img, double x, double y, double scale){
    drawImageBlurred(img, x+xScroll, y+yScroll, scale);
}

public void overlayImage(BufferedImage img, double x, double y, double scale){
    drawImage(img, x+xScroll, y+yScroll, scale);
}

public BufferedImage execute(BufferedImage img) {
    // TODO Auto-generated method stub
    float weight = 1.0f/2.0f;

    float [] elements = {weight, weight, weight, weight, weight, weight, weight, weight, weight};

    Kernel k = new Kernel (3,3,elements);
    ConvolveOp op = new ConvolveOp(k);

    BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());

    op.filter(img, dest);
    return dest;
}

public void drawImageBlurred(BufferedImage img, double x, double y, double scale){
    x -= xScroll;
    y -= yScroll;
    BufferedImage image = new BufferedImage((int)(img.getWidth()*scale), (int)(img.getHeight()*scale), BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.getGraphics();
    g.drawImage(img, 0, 0, (int)(img.getWidth()*scale), (int)(img.getHeight()*scale), null);
    execute(image);
    g2d.drawImage(image, (int)x, (int)y, null);
    g.dispose();
}

public void drawString(String s, Vector2D pos, int fontSize, int colour){
    drawString(s, pos.x, pos.y, fontSize, colour);
}

public void drawString(String s, double x, double y, int fontSize, int colour){
    if(s == null){
        return;
    }
    x -= xScroll;
    y -= yScroll;
    BufferedImage img = new BufferedImage(s.length()*fontSize+1, fontSize*2, BufferedImage.TYPE_INT_ARGB);
    Graphics g = img.getGraphics();
    g.setColor(new Color(colour));
    g.setFont(new Font("Consolas", Font.BOLD, fontSize));
    g.drawString(s, 0, img.getHeight()/2);
    g2d.drawImage(img, (int)x, (int)y, null);
    g.dispose();
}

public void drawImage(BufferedImage img, Vector2D pos, double scale){
    drawImage(img, pos.x, pos.y, scale);
}

public void drawLine(Vector2D v1, Vector2D v2, int colour, int width){
    drawLine(v1.x, v1.y, v2.x, v2.y, colour, width);
}

public void drawLine(double x1, double y1, double x2, double y2, int colour, int lWidth){
    x1 -= xScroll;
    y1 -= yScroll;
    x2 -= xScroll;
    y2 -= yScroll;
    g2d.setColor(new Color(colour));
    g2d.setStroke(new BasicStroke(lWidth));
    g2d.drawLine((int)x1, (int)y1, (int)x2, (int)y2);
}

public void drawImage(BufferedImage img, double x, double y, double scale){
    x -= xScroll;
    y -= yScroll;
    BufferedImage image = new BufferedImage((int)(img.getWidth()*scale), (int)(img.getHeight()*scale), BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.getGraphics();
    g.drawImage(img, 0, 0, (int)(img.getWidth()*scale), (int)(img.getHeight()*scale), null);
    g2d.drawImage(image, (int)x, (int)y, null);
    g.dispose();
}

public void drawRectangle(Vector2D pos, int xs, int ys, int colour){
    drawRectangle(pos.x, pos.y, xs, ys, colour);
}

public void drawRectangle(double x, double y, int xs, int ys, int colour){
    if(xs <= 0){
        return;
    }
    x -= xScroll;
    y -= yScroll;
    BufferedImage image = new BufferedImage(xs, ys, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = (Graphics2D) image.getGraphics();
    g.setColor(new Color(colour));
    g.fillRect(0, 0, xs, ys);
    g2d.drawImage(image, (int)x, (int)y, null);
    g.dispose();
}

public void drawImageRotated(BufferedImage img, Vector2D pos, double scale, double angle) {
    drawImageRotated(img, pos.x, pos.y, scale, angle);
}

 public void drawImageRotated(BufferedImage img, double x, double y, double scale, double angle) {
        x -= xScroll;
        y -= yScroll;  
        BufferedImage image = new BufferedImage((int)(img.getWidth() * 1.5D), (int)(img.getHeight() * 1.5D), 2);
        Graphics2D g = (Graphics2D)image.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.rotate(Math.toRadians(angle), image.getWidth() / 2, image.getHeight() / 2);
        g.drawImage(img, image.getWidth() / 2 - img.getWidth() / 2, image.getHeight() / 2 - image.getHeight() / 2, null);
        g2d.drawImage(image, (int)(x-image.getWidth()*scale/2), (int)(y-image.getHeight()*scale/2), (int)(image.getWidth()*scale), (int)(image.getHeight()*scale), null);
        g.dispose();      
 }

}

Как вы можете видеть в классе рендеринга, процесс визуализации выполняется столько раз, сколько возможно. Это должно дать игре максимально возможный FPS. Если вы пропустили то, что я прошу; Какие хорошие практики следует принимать во внимание при рендеринге вещей с использованием Java? И, что может быть причиной того, что бар для здоровья космических кораблей будет выглядеть так?

ПРИМЕЧАНИЕ: взгляните на это видео, я запустил эту программу и получил 80FPS с 50000 частицами, однако с моим кодом рендеринга (который, очевидно, имеет гораздо более низкое качество), я могу отображать всего лишь около 100 частиц, прежде чем вещи начинают испортиться.

http://www.youtube.com/watch?v=6M3Ze4Eu87Y

Это моя функция tick(), она получает название каждой игры (10 мс)

public void tick(){
    if(System.currentTimeMillis() - lastTime >= 1000){
        fps = frames;
        frames = 0;
        lastTime = System.currentTimeMillis();
    }
    frames++;
    if(renderer.trackedEntity != null){
        renderer.xScroll = renderer.trackedEntity.pos.x-SpaceGame.WIDTH/2;
        renderer.yScroll = renderer.trackedEntity.pos.y-SpaceGame.HEIGHT/2;
    }
    if(level != null && !paused){
        level.tick();
    }
    if(currentGui != null && currentGui.pausesGame()){
        paused = true;
    }else{
        paused = false;
    }
}
4b9b3361

Ответ 1

Я думаю, что ответ @mantrid должен решить вашу проблему. насколько производительность идет... в вашем коде есть некоторые очевидные "грехи":

Не рисуйте изображение в изображение, чтобы нарисовать изображение

public void drawImage(BufferedImage img, double x, double y, double scale){
    x -= xScroll;
    y -= yScroll;
    BufferedImage image = new BufferedImage((int)(img.getWidth()*scale), (int)(img.getHeight()*scale), BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.getGraphics();
    g.drawImage(img, 0, 0, (int)(img.getWidth()*scale), (int)(img.getHeight()*scale), null);
    g2d.drawImage(image, (int)x, (int)y, null);
    g.dispose();
}

Я не вижу смысла в этом. Почему бы просто не сделать это:

public void drawImage(BufferedImage img, double x, double y, double scale){
    g2d.drawImage(img, (int)(x-xScroll), (int)(y-yScroll), (int)(img.getWidth()*scale), (int)(img.getHeight()*scale), null);
}

AAAAAAAAAAAAA

Следующий на самом деле сожгли мои глаза

public void drawRectangle(double x, double y, int xs, int ys, int colour){
    if(xs <= 0){
        return;
    }
    x -= xScroll;
    y -= yScroll;
    BufferedImage image = new BufferedImage(xs, ys, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = (Graphics2D) image.getGraphics();
    g.setColor(new Color(colour));
    g.fillRect(0, 0, xs, ys);
    g2d.drawImage(image, (int)x, (int)y, null);
    g.dispose();
}

Почему? Это намного проще и быстрее:

public void drawRectangle(double x, double y, int xs, int ys, int colour){
    if(xs <= 0)
        return;

    g2d.setColor(new Color(colour));
    g2d.fillRect((int)(x-xScroll), (int)(y-yScroll), xs, ys);
}

То же самое относится к drawImageRotated и drawString. Просто прямо в буфер g2d, чего вы боитесь?

drawImageBlurred

Прежде всего... ты делаешь это снова! Во-вторых: вы, кажется, применяете операцию свертки к изображению на каждом кадре, почему бы просто не сделать эту операцию один раз (например, в начале приложения или еще лучше в программе редактирования изображений).

Я не имею в виду это совсем не так, но вы явно не очень разбираетесь в программировании вообще. Я думаю, вы можете взглянуть на обработку (http://processing.org). Я вроде как предвзятый, потому что я использую его сам почти каждый день. Это отличная среда обучения и прототипирования, написанная в java, которая должна позволить вам перестать думать о деталях реализации (например, переворачивать буферы) и сосредоточиться на том, что вам действительно нравится (сделать игру!).

Хорошо, надеюсь, что я сказал, имеет смысл. Удачи вам в кодировании!:)

Ответ 2

Панель работоспособности может быть сдвинута, потому что когда отставание в игре, xScroll и yScroll обновляются, а компоненты/оверлеи все еще отображаются в backbuffer. Чтобы исправить это:

1) перемещать игровые объекты пропорционально времени, прошедшему с последнего обновления, чтобы поддерживать постоянную скорость игры. добавьте параметр delta в level.tick() и все методы обновления игровых объектов

2) поместите level.tick() и game.render() в одну и ту же последовательность циклов, чтобы обеспечить обновление игровых объектов:

currentTime = System.currentTimeMillis();
delta = currentTime - lastTime; 

if(level != null && !paused){
    level.tick(delta); // introduce delta here
    game.render();
}

lastTime = currentTime;

В общем, рассмотрим следующие дополнительные шаги:

1) установите Component.setIgnoreRepaint(true), чтобы ускорить рендеринг

2) оптимизировать изображения для текущего отображения в начале

GraphicsConfiguration gc = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
Image optimized = gc.createCompatibleImage(img.getWidth(),img.getHeight(),Transparency.BITMASK);
optimized.getGraphics().drawImage(unoptimized, 0, 0, null);