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;
}
}
Ответы
Ответ 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);