Advertisement

Game rendering issue

Started by March 09, 2015 12:09 AM
2 comments, last by iSmokiieZz 9 years, 9 months ago

Hello,

This is my first post here. I'm very new to game programming. I'm trying to learn it using Java. I have been following this tutorial series on youtube for a little while, just fyi:

https://www.youtube.com/user/CodeNMore/videos

My issue is with a section of code with the tutorials provided. I did attempt to get an answer, but could not get enough information to solve it.

Basically the game loads tiles from a spritesheet made by this person for the player and the background. It is 128x128 and when I use the code from the tutorial and his spritesheet, it works flawlessly. The FPS is set to 60 and consistently stays at 60 during runtime.

The issue happens when I chose to try a different spritesheet. I used a few from this particular site just to test with. http://www.realmofdarkness.net/dq/games/nes/dw4/sprites. Some of the sheets work. All of the monsters spritesheets I have tried cause a severe slowdown in the game and cut the FPS literally in half. I have reviewed the image properties of both the spritesheet used in the tutorial and the one from this site and they seem the same to me.

I don't know what could be causing this issue. Below I will post what I believe to be the relevant sections of code and two profile screenshots from Netbeans, one with the game code running with the spritesheet from the video tutorial and one with the spritesheet from the site I was using.

Gameloop:


@Override
	public void run(){
	
		init();
		
		int fps = 60;
                // 1 billion nanoseconds is 1 second
		double timePerTick = 1000000000 / fps;
		double delta = 0;
		long now;
		long lastTime = System.nanoTime();
		long timer = 0;
		int ticks = 0;
		
                /* 
                    This game loop runs 60 times per second to achieve 60 FPS
                    Each loop is a frame
                    VARIABLE FRAME RATE 
                
                */
		while(running){
			now = System.nanoTime();
                        // delta is how much time that is required to pass until the loop runs tick and render
                        // In mathematics, the term Delta is used to describe a change in something.
			delta += (now - lastTime) / timePerTick;
                        // timer is for the display of FPS
			timer += now - lastTime;
                        // since delta updated, set lastTime to now for next check
			lastTime = now;
			
			if(delta >= 1){
				tick();
				render();
//                                if(delta >= 2){
//                                    render();   
//                                }
				ticks++;
				delta--;
			}
                        
                        
			
			if(timer >= 1000000000){
				System.out.println("Ticks and Frames: " + ticks);
				ticks = 0; // resets tick
				timer = 0; // resets timer to dislay again
			}
		}
		
		stop();
		
	}

ImageLoader Class:


package dev.codenmore.tilegame.gfx;

import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageLoader {

	public static BufferedImage loadImage(String path){
		try {
			return ImageIO.read(ImageLoader.class.getResource(path));
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		return null;
	}
	
}
 

Assets class:


package dev.codenmore.tilegame.gfx;

import java.awt.image.BufferedImage;

public class Assets {
private static final int width = 32, height = 32;
    private static final int WIDTH = 48, HEIGHT = 48;	
	public static BufferedImage player, dirt, grass, stone, tree;

	public static void init(){
		//SpriteSheet sheet = new SpriteSheet(ImageLoader.loadImage("/textures/sheet2.png"));
                SpriteSheet sheet = new SpriteSheet(ImageLoader.loadImage("/textures/monsters.png"));
		
		player = sheet.crop(0, 0, width, height);
		dirt = sheet.crop(width, 0, width, height);
		grass = sheet.crop(width * 2, 0, width, height);
		stone = sheet.crop(width * 3, 0, width, height);
		tree = sheet.crop(0, height, width, height);
                
//                player = sheet.crop(200, 200, width, height);
//                dirt = sheet.crop(WIDTH * 3, 16, WIDTH, HEIGHT);
//                tree = sheet.crop(144, 128, WIDTH, HEIGHT);
//                grass = sheet.crop(0, 128, WIDTH, HEIGHT);
//                stone = sheet.crop(144, 80, WIDTH, HEIGHT);
	}	
            
}
 

SpriteSheet class:


package dev.codenmore.tilegame.gfx;

import java.awt.image.BufferedImage;

public class SpriteSheet {

	private BufferedImage sheet;
	
	public SpriteSheet(BufferedImage sheet){
		this.sheet = sheet;
	}
	
	public BufferedImage crop(int x, int y, int width, int height){
		return sheet.getSubimage(x, y, width, height);
	}
	
}

Worlds class:


package dev.codenmore.tilegame.worlds;

import java.awt.Graphics;

import dev.codenmore.tilegame.Game;
import dev.codenmore.tilegame.tiles.Tile;
import dev.codenmore.tilegame.utils.Utils;

public class World {

	private Game game;
	private int width, height;
	private int spawnX, spawnY;
	private int[][] tiles;
	
	public World(Game game, String path){
		this.game = game;
		loadWorld(path);
	}
	
	public void tick(){
		
	}
	
	public void render(Graphics g){
		for(int y = 0;y < height;y++){
			for(int x = 0;x < width;x++){
				getTile(x, y).render(g, (int) (x * Tile.TILEWIDTH - game.getGameCamera().getxOffset()),
						(int) (y * Tile.TILEHEIGHT - game.getGameCamera().getyOffset()));
			}
		}
	}
	
	public Tile getTile(int x, int y){
		Tile t = Tile.tiles[tiles[x][y]];
		if(t == null)
			return Tile.dirtTile;
		return t;
	}
	
	private void loadWorld(String path){
		String file = Utils.loadFileAsString(path);
		String[] tokens = file.split("\\s+");
		width = Utils.parseInt(tokens[0]);
		height = Utils.parseInt(tokens[1]);
		spawnX = Utils.parseInt(tokens[2]);
		spawnY = Utils.parseInt(tokens[3]);
		
		tiles = new int[width][height];
		for(int y = 0;y < height;y++){
			for(int x = 0;x < width;x++){
				tiles[x][y] = Utils.parseInt(tokens[(x + y * width) + 4]);
			}
		}
	}
	
}








Tiles class:


package dev.codenmore.tilegame.tiles;

import java.awt.Graphics;
import java.awt.image.BufferedImage;

public class Tile {
	
	//STATIC STUFF HERE
	
	public static Tile[] tiles = new Tile[256];
	public static Tile grassTile = new GrassTile(0);
	public static Tile dirtTile = new DirtTile(1);
	public static Tile rockTile = new RockTile(2);
	
	//CLASS
	
	public static final int TILEWIDTH = 64, TILEHEIGHT = 64;
	
	protected BufferedImage texture;
	protected final int id;
	
	public Tile(BufferedImage texture, int id){
		this.texture = texture;
		this.id = id;
		
		tiles[id] = this;
	}
	
	public void tick(){
		
	}
	
	public void render(Graphics g, int x, int y){
		g.drawImage(texture, x, y, TILEWIDTH, TILEHEIGHT, null);
	}
	
	public boolean isSolid(){
		return false;
	}
	
	public int getId(){
		return id;
	}
	
}
 

These are using the tutorial based spritesheet

[attachment=26334:Workspace 1_008.png]

[attachment=26333:Workspace 1_009.png]

These are using the non-tutorial based spritesheet

[attachment=26332:Workspace 1_010.png]

[attachment=26331:Workspace 1_011.png]

I don't profess to know java very well but isn't this using the buffered image class in such a way that Each time you draw the sprite it decodes it from jpeg, png, or gif data in memory, and plots it? Rather than decoding it once on load to raw pixels then plotting those each time?

This would be incredibly inefficient and render times would vary drastically depending on the image format chosen for the sprite sheet.

Any way you can check this?

Advertisement

Thanks for the reply braindigitalis! I believe it only decodes them once. Using Image Loader it decodes the spritesheet file and loads it into a BufferedImage SpriteSheet object located in the Assets class. Assets.init() is called once in the Game class . After that a graphics object calls a method called drawString() that will use the Assets object to display the image. I took a look at this link to see where it decodes and I guess it's between the Image Loader and Buffered Image.

http://docs.oracle.com/javase/tutorial/2d/images/loadimage.html

Game class init section:


private void init(){
		display = new Display(title, width, height);
		display.getFrame().addKeyListener(keyManager);
		Assets.init();
		
		gameCamera = new GameCamera(this, 0, 0);
		
		gameState = new GameState(this);
		menuState = new MenuState(this);
		State.setState(gameState);
	}


I have reviewed the image properties of both the spritesheet used in the tutorial and the one from this site and they seem the same to me.

Just had a look at them. The image of the tutorial (I took the one from his github) seems to be 32bit png, thus having an alpha channel. The monster ones are 24bit, w/o alpha. I don't know much about java gui stuff, but drawImage() might do a bitblt to convert the BufferedImage pixel-by-pixel if the image color depth doesn't match with the desired one. You could try converting one of these monster spritesheets to a 32bit image and see if it works faster.

This topic is closed to new replies.

Advertisement