Game loop taking around 30% of CPU

Hi, I was looking at the game loop tutorial and put it in a simple tile map based demo. I noticed that it consumes around 30% of the CPU. Is this value expected? Minecraft consumes exactly the same in my machine, but the game is much heavier. Just wondering if I could optimize my cpu consuption. Here is the code if anyone want to try, can run in window mode or applet.


import java.applet.Applet;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.net.URL;

import javax.imageio.ImageIO;

public class TileMap1 extends Applet implements Runnable {
	private static final int SCREEN_WIDTH = 8 * 32;
	private static final int SCREEN_HEIGHT = 7 * 32;
	private boolean isApplet;
	private Thread gameThread;
	private int fps;
	private BufferedImage tiles;
	private Image offImage;
	private int[][] myMap = { { 1, 1, 1, 1, 1, 1, 1, 1 }, //
			{ 1, 0, 0, 0, 0, 0, 0, 1 }, //
			{ 1, 0, 1, 0, 0, 0, 0, 1 }, //
			{ 1, 0, 0, 0, 0, 1, 0, 1 }, //
			{ 1, 0, 0, 0, 0, 0, 0, 2 }, //
			{ 1, 1, 1, 1, 1, 1, 1, 1 } };

	private int[][] myMap2 = { { 1, 1, 1, 1, 1, 1, 1, 1 }, //
			{ 1, 0, 0, 0, 0, 0, 0, 1 }, //
			{ 1, 0, 1, 0, 0, 0, 0, 1 }, //
			{ 1, 0, 0, 0, 0, 1, 0, 1 }, //
			{ 3, 0, 0, 0, 0, 0, 0, 1 }, //
			{ 1, 1, 1, 1, 1, 1, 1, 1 } };

	private int[][] currentMap;
	private Sprite hero;
	private boolean[] controls = new boolean[5];

	public TileMap1() {
		this.hero = new Sprite(2, 1);
		this.isApplet = true;
		this.currentMap = this.myMap;
		super.enableEvents(KeyEvent.KEY_EVENT_MASK);
	}

	public void init() {
		this.offImage = super.createImage(SCREEN_WIDTH, SCREEN_HEIGHT);
		try {
			URL url = this.getClass().getClassLoader().getResource("tiles.png");
			this.tiles = ImageIO.read(url);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}
	}

	public void start() {
		if (this.gameThread == null) {
			this.gameThread = new Thread(this);
		}
		this.gameThread.start();
	}

	public void stop() {
		this.gameThread = null;
	}

	public void paint(Graphics g) {
		this.render(this.offImage.getGraphics());
		g.drawImage(this.offImage, 0, 0, 640, 480, null);

	}

	private void render(Graphics g) {
		g.setColor(Color.black);
		g.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

		// Draws our map.
		int mapWidth = this.currentMap[0].length;
		int mapHeight = this.currentMap.length;
		for (int i = 0; i < mapWidth; i++) {
			for (int j = 0; j < mapHeight; j++) {
				int frame = this.currentMap[j][i];
				g.drawImage(this.tiles.getSubimage(frame * 32, 0, 32, 32),
						i * 32, j * 32, null);
			}
		}

		hero.render(g);

		g.setColor(Color.red);
		g.drawString("FPS:" + this.fps, 10, 10);

	}

	public void run() {
		long lastLoopTime = System.nanoTime();
		final int TARGET_FPS = 60;
		final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
		long lastFpsTime = 0;
		int fps = 0;

		while (true) {
			long now = System.nanoTime();
			long updateLength = now - lastLoopTime;
			lastLoopTime = now;
			double delta = updateLength / ((double) OPTIMAL_TIME);

			// update the frame counter
			lastFpsTime += updateLength;
			fps++;
			// System.out.println(lastFpsTime);
			// update our FPS counter if a second has passed since
			// we last recorded
			if (lastFpsTime >= 1000000000) {
				// System.out.println("(FPS: " + fps + ")");
				this.fps = fps;
				lastFpsTime = 0;
				fps = 0;
			}

			this.logic();
			this.paint(this.getGraphics());
			Toolkit.getDefaultToolkit().sync();

			try {
				Thread.sleep((lastLoopTime - System.nanoTime() + OPTIMAL_TIME) / 1000000);
			} catch (Exception e) {
			}
		}
	}

	private void checkDoor(int j, int i) {
		if (this.currentMap[j][i] == 2) {
			this.currentMap = myMap2;
			hero.x = 1 * 32 + 6;
		} else if (this.currentMap[j][i] == 3) {
			this.currentMap = myMap;
			hero.x = 6 * 32 + 6;
		}
	}

	private void logic() {
		int dx = 0;
		int dy = 0;
		if (this.controls[0]) {
			dx = -1;
		} else if (this.controls[1]) {
			dx = 1;
		} else if (this.controls[2]) {
			dy = -1;
		} else if (this.controls[3]) {
			dy = 1;
		}
		hero.move(dx, dy);
	}

	protected void processKeyEvent(KeyEvent e) {
		int[] keys = new int[] { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT,
				KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_SPACE };
		for (int i = 0; i < keys.length; i++) {
			if (e.getKeyCode() == keys[i]) {
				this.controls[i] = e.getID() == KeyEvent.KEY_PRESSED;
			}
		}
		if (e.getKeyCode() == KeyEvent.VK_ESCAPE && !this.isApplet) {
			System.exit(0);
		}
	}

	public void buildFrame() {
		Frame f = new Frame("Tile Based Maps Tutorial");
		f.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		f.setSize(640, 480);
		f.setResizable(false);
		Dimension dimension = getToolkit().getScreenSize();
		Rectangle rectangle = f.getBounds();
		f.setLocation((dimension.width - rectangle.width) / 2,
				(dimension.height - rectangle.height) / 2);
		f.add(this);
		f.setVisible(true);
		this.isApplet = false;
		this.init();
		this.start();
	}

	public static void main(String[] args) {
		TileMap1 g = new TileMap1();
		g.buildFrame();
	}

	class Sprite {
		int x;
		int y;

		int dx;
		int dy;

		Sprite(int px, int py) {
			x = px * 32 + 6;
			y = py * 32 + 6;
		}

		void move(int pDx, int pDy) {
			dx = pDx;
			dy = pDy;

			if (pDy != 0) {
				if (canMove(x, y + dy)) {
					checkDoor((int) Math.floor(y / 32),
							(int) Math.floor(x / 32));
					y += dy;
				}
			}

			if (pDx != 0) {
				if (canMove(x + dx, y)) {
					checkDoor((int) Math.floor(y / 32),
							(int) Math.floor(x / 32));
					x += dx;
				}
			}
		}

		boolean canMove(int x, int y) {
			int downY = (int) Math.floor((y + 20) / 32);
			int upY = (int) Math.floor(y / 32);
			int leftX = (int) Math.floor(x / 32);
			int rightX = (int) Math.floor((x + 20) / 32);

			if (dy == -1) {
				return (!isSolid(upY, leftX) && !isSolid(upY, rightX));
			} else if (dy == 1) {
				return (!isSolid(downY, leftX) && !isSolid(downY, rightX));
			}

			if (dx == -1) {
				return (!isSolid(downY, leftX) && !isSolid(upY, leftX));
			} else if (dx == 1) {
				return (!isSolid(upY, rightX) && !isSolid(downY, rightX));
			}

			return false;
		}

		private boolean isSolid(int j, int i) {
			return (currentMap[j][i] == 1);
		}

		void render(Graphics g) {
			// Body
			g.setColor(Color.YELLOW);
			g.fillOval(x, y, 20, 20);
			g.setColor(Color.BLACK);
			g.drawOval(x, y, 20, 20);

			// Eyes
			g.setColor(Color.WHITE);
			g.fillOval(x + 2, y + 5, 8, 10);
			g.fillOval(x + 10, y + 5, 8, 10);
			g.setColor(Color.BLACK);
			g.drawOval(x + 2, y + 5, 8, 10);
			g.drawOval(x + 10, y + 5, 8, 10);
			g.fillOval(x + 4 + (dx * 2), y + 8 + (dy * 2), 4, 4);
			g.fillOval(x + 12 + (dx * 2), y + 8 + (dy * 2), 4, 4);
		}
	}
}

Sleep time is relative, but the key to not absorbing a lot of CPU time is to increase the time that it sleeps. It totally depends on the computer, but the more time in general you allow your program to sleep, the lower the CPU usage is going to be. (Besides, you have to leave time to allow your computer to do other processes.)

Another, slightly less problem for CPU time, is graphics. Usually, you want to make as little graphic calls as possible. (This is why in OpenGL they are so insistent over VBO’s and VAO’s.) Reducing the calls made to draw things onto the screen will also improve CPU time.

Lastly, try to stay away from excessive looping whenever possible. Recursive loops can creep up on CPU time pretty quickly. These are usually minor and only become a problem when used in excess.

Those are the 3 major culprits when it comes to gaming that I see really take a lot out of the CPU. As long as you aren’t designing your classes badly, there shouldn’t be a problem.

As for your code, look for the examples within this site that lay out some pretty good techniques. Optimizing code is a trap that is possibly best avoided. Much better to try and get your game working and tweaking it if it is not performing to the standard you want it to.

Thanks for the tips. The code for the loop I got from this site under the articles sections. But still not sure if this is the best option and also if this simple could that I posted could be optimized?

When I was still using Java2D I slept for 1 millisecond at a time until the extra time was used up (I believe it increased the accuracy), then I moved to sleeping for 1 millisecond for 80% of the extra time and then yielding for the rest of the time. I believe I also used aspects from other game loops I’ve seen here on JGO (sorry if my example includes someones code, I don’t have a source) Something like this:


 // Disclaimer: I haven't looked at this code in a while and it likely isn't the best solution, just an example of what seemed to work for me

 while (running) {
   long start, timePassed;
   start = System.nanoTime();
   // Update until we're caught up (hopefully just once)
   for (int updates = 0; (timePassed = start - lastUpdate) > waitTime && updates < MAX_UPDATES_PER_TICK; updates++) {
      // Update here
      lastUpdate += waitTime;
   }

   // Render

   while ((timePassed = System.nanoTime() - lastUpdate) < waitTime) {
      if (timePassed <= waitTime * 0.8)
         Util.sleep(1);
      else
         Thread.yield();
   }
}

I also used changed a few lines in the Sync class that Riven wrote for LWJGL so that it worked without LWJGL and used that for a while. It seemed to work pretty well. With that you just have to do something like this:


// Really simple example
while (running) {
   // Update
   // Render
   Sync.sync(60) // Or whatever FPS you want
}

EDIT: I forgot to mention that I have since moved to LWJGL and now that I’ve adjusted to the differences, I love it. I would highly recommend LWJGL if you want to mess around with more low level stuff and do more things yourself, or LibGDx if you want some higher level functionality already implemented for you and access to the low level bits as well.

Minecraft uses the most performance wise rendering methods available. You’re using shit java2d. You need to go learn libGDX (Probably better options for starters) or LWJGL(More advanced options)