Rendering and Scale Issue

Good day, me hearties!

Let’s get straight to it. I’ve made a game that’s composed of three class files: Game.java, Screen.java, and Map.java.

Game.java: game loop, etc

Screen.java: renders a map

Map: the map

Alright. Pretty basic, eh? Right. That’s what I was hoping for.

Issue starts here: THE SCREEN IS SCALED AFTER RENDERING PROCESS COMPLETES IN ORDER TO SAVE CPU POWER.

Not much of a problem, it seems. Well, read on.

In order to calculate the width/height (in pixels) of my tile-map, I simply multiplied the tile size by the width/height in tiles. This would be accurate, but after the scaling (x3), this data is completely wrong. A coordinate before the scaling doesn’t represent the same thing it did after the scaling. I implemented a player & camera, not realizing that my coordinate system was entirely nullified, and I began experiencing many issues, such as not being able to calculate when the player reaches the edge of the map.

Keep in mind that the player was a BufferedImage rendered on top of the Screen/Map (also a BufferedImage).

How can I retain this scale and accurately implement a coordinate system? Should I somehow merge the player image into the map image, resulting in both items being scaled? This seems sensible, yet how would I carry it out?

Game.java:

package com.mizzath.game;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;

import com.mizzath.graphics.*;

public class Game extends Canvas implements Runnable {
	private static final long serialVersionUID = 1L;
	
	int width = 300;
	int height = width / 12 * 9;
	int scale = 3;
	String title = "Game";
	
	boolean running = false;
	
	BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
	int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
	
	JFrame frame;
	Thread thread;
	Screen screen;
	
	Map map;
	
	public Game() {
		frame = new JFrame(title);
		
		map = new Map(64, 64, 16);
		screen = new Screen(width, height, map);
		
		Dimension size = new Dimension(width * scale, height * scale);
		
		setSize(size);

		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);
		frame.add(this);
		frame.pack();
		frame.setVisible(true);
		
		start();
	}
	
	public void run() {
		long start = System.nanoTime();
		long timer = System.currentTimeMillis();
		double period = 1000000000D / 60D;
		double delta = 0;
		
		int updates = 0; // Logic
		int frames = 0;  // Rendering
		
		while(running) {
			long now = System.nanoTime();
			
			delta += (now - start) / period;
			
			start = now;
			
			if (delta > 1) {
				updates++;
				update();
				delta--;
			}
			
			frames++;
			render();
			
			if (System.currentTimeMillis() - timer > 1000) {
				frame.setTitle(title + " | Logical updates: " + updates + ", Graphical Updates: " + frames);
				
				timer += 1000;
				updates = 0;
				frames = 0;
			}
		}
	}
	
	public void render() {
		BufferStrategy strategy = getBufferStrategy();
		
		if (strategy == null) {
			createBufferStrategy(3);
			return;
		}
		
		screen.render(0, 0);
		
		for (int i = 0; i < pixels.length; i++) {
			pixels[i] = screen.pixels[i];
		}
		
		Graphics g = strategy.getDrawGraphics();
		
		g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
		
		g.dispose();
		strategy.show();
	}
	
	public void update() {
		
	}
	
	public void start() {
		running = true;
		
		thread = new Thread(this, "Display");
		thread.start();
	}
	
	public void stop() {
		running = false;
		
		try {
			thread.join();
		} catch (InterruptedException e) {
			
		}
	}

	public static void main(String[] args) {
		Game game = new Game();
	}
}

Screen.java:

package com.mizzath.graphics;

public class Screen {
	private int width, height;
	
	private Map map;
	
	public int[] pixels;
	
	public Screen(int width, int height, Map map) {
		this.width = width;
		this.height = height;
		this.map = map;
		
		pixels = new int[width * height];
	}
	
	public void render(int xOffset, int yOffset) {
		for (int y = 0; y < height; y++) {
			int yy = y + yOffset;
			if (yy < 0 || yy >= map.heightInPixels()) break;
			
			for (int x = 0; x < width; x++) {
				int xx = x + xOffset;
				if (xx < 0 || xx >= map.widthInPixels()) break;
				
				int tileIndex = (xx >> (map.tileSize() / 4)) + (yy >> (map.tileSize() / 4)) * map.widthInTiles();
				
				if (tileIndex < map.tiles().length) {
					pixels[x + y * width] = map.tiles()[tileIndex];
				}
			}
		}
	}
}

Map.java:

package com.mizzath.graphics;

import java.util.Random;

public class Map {
	private int widthInTiles, heightInTiles, tileSize;
	private int[] tiles;
	
	private Random random = new Random();
	
	public Map(int widthInTiles, int heightInTiles, int tileSize) {
		this.widthInTiles = widthInTiles;
		this.heightInTiles = heightInTiles;
		this.tileSize = tileSize;
		
		tiles = new int[widthInTiles * heightInTiles];
		
		for (int i = 0; i < tiles.length; i++) {
			tiles[i] = random.nextInt(0xffffff);
		}
	}
	
	public int[] tiles() {
		return tiles;
	}
	
	public int tileSize() {
		return tileSize;
	}
	
	public int widthInTiles() {
		return widthInTiles;
	}
	
	public int heightInTiles() {
		return heightInTiles;
	}
	
	public int widthInPixels() {
		return widthInTiles * tileSize;
	}
	
	public int heightInPixels() {
		return heightInTiles * tileSize;
	}
}

Thanks, mates!

I don’t want to be rude, but what is with everyone using all this pixel[] nonsense? Unless you are trying to do some super ridiculous effects in java2D or are working on a java4k game there is really no need to touch an array of pixels.

Look into Graphics2D.scale I think. It may help.

Also, I recommended not over thinking this too much. When I wanted to do some pixel effects it java2D was slow. So I rendered everything like normal to a Volatile Image at full resolution. This keeps things fast. After the rendering was done and I wanted to access the pixels for lighting or w/e, I then drew the volatile image to a bufferedimage at the a reduced resolution. Thing are still fast. Now I access the pixels from the bufferedimage which un-managed it. This is were the slow down happens but because the resolution is low, it is manageable. After doing my black pixel magic, I draw the edited buffered image using the buffer strategies graphics object.

You could use two volatile images instead of bufferedImage and us getSnapShot() but I am not sure if that would be much of a performance boost. The key thing is keeping things managed. It is hard to beat the performance of the simple, g.drawImage() as it keeps everything managed. The moment you touch pixel[], BAM! unmanaged. If you only do this every once in a while it is no problem but if you do it every frame…

Believe me, I’m absolutely new when it comes to Java game development, so input like this is truly appreciated. I’ll research a VolatileImage for a while and then attempt to implement it. However, how simple would it be to recreate my tile map?