Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas - good rendering practices?

Tags:

java

rendering

I've been using the Canvas a lot lately for little Java games and i've noticed a lot of strange things can happen. For example, earlier today I created a little game in which the objective is to shoot enemy ships and get points. The game draws tiny 32x32 images on the screen (sometimes slightly bigger) and for a reason I am oblivious to, the game renders oddly.

For example, my ship has a health bar above it's head:

enter image description here

As you can see by the image, the textures are really small. Despite this, the game lags and sometimes things render incorrectly like this for example:

enter image description here

If you look closely at the top of the health bar, you can see that it's been shifted upwards slightly, it puzzles me how his happens as all of my rendering is buffered.

My rendering code:

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

}

And my "Render.class" if you need to study it:

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

}

As you can see in the rendering class, the render process is done as many times a second as possible. This is to give the game the highest possible FPS. If you missed the point of what I'm asking; What good practices should I take into account when rendering stuff using Java? And, what could possibly be causing the space ships health bar to render like so?

NOTE: take a look at this video, I ran that program and got 80FPS with 50000 particles, however with my rendering code (which is obviously of a much lower quality) I can render only a mere 100 or so particles, before things start messing up.

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

This is my tick() function, it gets called every game tick (10ms)

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;
    }
}
like image 678
Shaun Wild Avatar asked Feb 10 '13 14:02

Shaun Wild


People also ask

What is canvas rendering?

Canvas rendering is obviously the exact opposite of a semantically rich web page. It's a black box that gives the browser no information or context about what's happening inside.

Is canvas more performant than Dom?

In short, the canvas and WebGL are more performant than the DOM, and with third-party libraries, its ease-of-use is comparable; furthermore, growing browser support for additional web standards have the potential to further boost canvas performance.

Is HTML canvas fast?

The Canvas tab loaded in one second and takes up 30MB. It also takes up 13% of CPU time all of the time, regardless of whether or not one is looking at it. Video on the HTML page, while I am not moving objects, is actually perfectly smooth.


2 Answers

i think the answer by @mantrid should fix your problem. as far as performance goes ... there are some obvious "sins" in your code:

Don't draw an image into an image to draw an image

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

I don't see the point of this. Why not just do this:

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

The next one actually burnt my eyes

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

Why? This is so much simpler and quicker:

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

The same goes for the drawImageRotated and drawString. Just draw to the g2d buffer directly, what are you afraid of?

drawImageBlurred

First of all... you're doing it again! Second: You seem to be applying a convolve operation to an image on each frame, why not just do that operation once (e.g. at the start of the app, or even better yet, in an image editing program).

I don't mean this in a bad way at all, but you are clearly not very experienced with programming in general. I think you could take a look at processing (http://processing.org). I'm sort of biased here because i'm using it myself almost everyday. It is a great learning and prototyping environment written in java that should allow you to stop thinking about implementation details (like flipping buffers) and focus on what you really care for (make a game!).

Alright, hope what i said makes sense. Good luck with your coding! :)

like image 176
kritzikratzi Avatar answered Sep 16 '22 19:09

kritzikratzi


The health bar might be shifted because when game lags, xScroll and yScroll are updated while components/overlays are still being rendered into backbuffer. To fix that:

1) move game objects proportionally to amout of time elapsed from latest update to keep game speed always constant. add delta parameter to level.tick() and all game objects update method

2) put level.tick() and game.render() within the same loop sequence to ensure game objects are updated first:

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

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

lastTime = currentTime;

In general, consider these additional steps:

1) set Component.setIgnoreRepaint(true) to speed up rendering

2) optimize images for current display at the start

GraphicsConfiguration gc = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
Image optimized = gc.createCompatibleImage(img.getWidth(),img.getHeight(),Transparency.BITMASK);
optimized.getGraphics().drawImage(unoptimized, 0, 0, null);
like image 44
mantrid Avatar answered Sep 16 '22 19:09

mantrid