Delta speed and some distance fun.

Hey guys,

I’ve been lurking about on these forums for some time now, and just now decided to register, because no amount of searching and trail and error would yield and answer to me. :frowning:

So I’m trying to make a simple WormChase game(Like the one in “Killer Game Programming in Java”), but I can’t really get the speed of my snake to be independent of how many times I call the snakes tick() function. So I’d love some help on this. :slight_smile:

Also, I’m trying to paint is bodypart of the snake so that they just barely touch each other(I suspect this’ll be a problem when trying to adjust move speed), because I want to try and add graphics to the snake(Using sprites). Some help on how to position the body parts would be awesome too(Or rather, how to calculate the new position of the head)

My 2 classes are here:

WormChase.java


import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;

public class WormChase extends Canvas implements Runnable {
	private static final long serialVersionUID = 1L;

	private static final String GAME_TITLE = "WormChase";

	private static final int WIDTH = 500;
	private static final int HEIGHT = 400;

	private boolean isRunning = false;

	private volatile boolean isPaused = false;

	private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

	private int frames = 0, fps = 0;
	private int ticks = 0, tps = 0;

	private Worm bob;

	private void start() {
		isRunning = true;
		new Thread(this).start();
	}

	public void run() {

		init();

		long lastLoopTime = System.nanoTime();
		double unprocessed = 0;
		double nsPerTick = 1000000000.0 / 10.0;

		long fpsTimer = lastLoopTime;

		while (isRunning) {
			long now = System.nanoTime();
			unprocessed += (now - lastLoopTime) / nsPerTick;
			long delta = now - lastLoopTime;
			lastLoopTime = now;

			while (unprocessed >= 1) {
				unprocessed--;
				tick(delta);
			}

			render();

			if (System.nanoTime() - fpsTimer > 1000000000) {
				fps = frames;
				frames = 0;
				tps = ticks;
				ticks = 0;
				fpsTimer += 1000000000;
			}

			try {
				Thread.sleep(2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		System.exit(0);
	}

	private void init() {
		bob = new Worm(WIDTH, HEIGHT, 1f, 40);
	}

	private void tick(long delta) {
		if (!isPaused) {
			// move bob
			bob.tick(delta);
			// Update tick counter
			ticks++;
		}
	}

	private void render() {
		BufferStrategy bs = getBufferStrategy();
		if (bs == null) {
			createBufferStrategy(3);
			return;
		}

		Graphics g = bs.getDrawGraphics();
		g.drawImage(image, 0, 0, WIDTH, HEIGHT, null);

		// draw bob
		bob.render(g);

		// draw fps/ticks
		g.setColor(Color.white);
		g.drawString("fps: " + fps + " ticks:" + tps, 20, 25);

		g.dispose();
		bs.show();

		// Update frame counter
		frames++;
	}

	/**
	 * Toggles the state of pause
	 */
	public void togglePauseState() {
		isPaused = !isPaused;
	}

	public void testPress(int x, int y) {
		togglePauseState();
	}

	public static void main(String[] args) {
		final WormChase wc = new WormChase();

		wc.setPreferredSize(new Dimension(WIDTH, HEIGHT));
		wc.setMaximumSize(new Dimension(WIDTH, HEIGHT));
		wc.setMinimumSize(new Dimension(WIDTH, HEIGHT));
		wc.setIgnoreRepaint(true);

		JFrame frame = new JFrame(GAME_TITLE);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		Container v = frame.getContentPane();
		v.add(wc);

		frame.pack();

		frame.setFocusable(true);
		frame.requestFocus();

		frame.setResizable(false);
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);

		wc.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
//				System.out.println("You clicked, master.");
				wc.testPress(e.getX(), e.getY());
			}
		});

		wc.start();
	}
}

And Worm.java


import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.util.Random;

public class Worm {
	private static Random random = new Random();
	private static final int DOTSIZE = 12;
	private final int MAX_SIZE;

	private final int w, h;

	private Point2D.Double[] wormBody;
	private int curDir;
	private int curSize = 0;
	private int tailPos = -1;
	private int headPos = -1;
	private float speed;

	public Worm(int w, int h, float speed, int max_size) {
		this.w = w;
		this.h = h;
		this.speed = speed;

		MAX_SIZE = max_size;
		wormBody = new Point2D.Double[MAX_SIZE];
		curDir = random.nextInt(360);
	}

	public void render(Graphics g) {
		if (curSize > 0) {
			g.setColor(Color.gray);
			int i = tailPos;
			while (i != headPos) {
				g.fillOval((int)wormBody[i].x, (int)wormBody[i].y, DOTSIZE, DOTSIZE);
				i = (i + 1) % MAX_SIZE;
			}
			
			g.setColor(Color.red);
			g.fillOval((int)wormBody[headPos].x, (int)wormBody[headPos].y, DOTSIZE, DOTSIZE);
		}
	}

	public void tick(long delta) {
		int prevPos = headPos;
		headPos = (headPos + 1) % MAX_SIZE; // Wrapping if needed

		if (curSize == 0) {
			wormBody[headPos] = new Point2D.Double(w / 2, h / 2);
			curSize++;
			tailPos = headPos;
		} else if (curSize == MAX_SIZE) {
			wormBody[headPos] = newPos(prevPos, delta);
			tailPos = (tailPos + 1) % MAX_SIZE;
		} else {
			wormBody[headPos] = newPos(prevPos, delta);
			curSize++;
		}
	}

	/**
	 * This is where the new head position is being calculated.
	 */
	private Point2D.Double newPos(int prevPos, long delta) {
		double ds = delta / 10000000D * speed;
		double dirInRad = Math.toRadians(curDir);
		double x = wormBody[prevPos].x + Math.cos(dirInRad) * ds + Math.cos(dirInRad) * (DOTSIZE-2);
		double y = wormBody[prevPos].y + Math.sin(dirInRad) * ds + Math.sin(dirInRad) * (DOTSIZE-2);

		if (x < 0) {
			x += w;
		} else if (x >= w) {
			x -= w;
		}

		if (y < 0) {
			y += h;
		} else if (y >= h) {
			y -= h;
		}

		curDir += random.nextInt(90) - 45;

		return new Point2D.Double(x, y);
	}

	public boolean touchedHead(int x, int y) {
		return false;
	}

	public boolean touchedBody(int x, int y) {
		return false;
	}
}

Any help would be awesome! :slight_smile:

What is wrong with your current implementation of a deltaTime?

EDIT: Also, instead of calling cos/sin twice (which hurts performance a lot!), just do: cos/sin(dir) * (ds + DOTSIZE - 2)

If my ticks per second go up, then the speed of the worm goes op too, without me changing the speed variable. I want the speed to be dependent on the speed var, and not on the number of ticks. :slight_smile:

And good catch about the cos/sin. I thought I had done that already heh, thanks.

Ok, I figured out what you are doing wrong.
You oddly combined the two approaches to such a thing(how to handle time in a render/logic loop), either u can choose ,for such a loop, a fixed or dynamically time interval on which the changes get calculated.
Not both as you did.

[quote]What u call ticks, should be fixed time interval. i.e. you want that some logic (or rendering) should be done for example every 10ms.

Dynamically chosen intervals use some sort of time delta for the calculations of the next change.

So why do we have those two different things? The answer is that some loops need different runtime time behavior then others.

Render loops need dynamic time intervals, because the calculations for the rendering can take a lot of time and can vary a lot in the same application. So if u would use a fixed time step you would notice slow downs of the simulated time if the rendering takes longer then your fixed time step.
You mustn’t set a lower limit on the time interval of a render loop only a upper, if you want to.

On the other hand a fixed time interval is quite useful for logic or physic processing, because a fixed interval makes the outcome more predictable and stable.
[/quote]
To come back to your problem, the simple answer is: replace

tick(delta);

with

tick(nsPerTick);

the long:
either choose a fixed interval, then u would get rid of the delta in your update method of the worm and use a constant.

Or you get rid of all your tick code and just use the delta.

also some suggestions:

  • get rid of the System.exit(0); in your thread

  • I like your general coding style, only thing I saw was the capitalized MAX_SIZE(it is no constant)

  • nsPerTick on the other hand is

  • try to not couple presentation with logic, the game playfield size shouldn’t be linked to the size of your window(the logic shouldn’t worry about how big the window is, the window should worry about how big the game wants to be)

  • do the decoupling even further, one class for rendering one for data von for logic (MVC pattern)

Haha, I think I’ve gotten something mixed up, eh? :stuck_out_tongue: Thanks for clearing up the tick() thing! :slight_smile: Now my worms can actually move with different speeds and stuff. (Though it required that I removed the thing that made each circle just barely touch the other one. I’ll have to figure out another way of doing that, so it wont affect the speed. I’m thinking that using a circular buffer isn’t the way to go - I’ll probably have to move each circle, everytime)

I removed the System.exit(0) and made max_size lowercase. It started out as being a constant, that’s why it was uppercase. :slight_smile:

I’m not entirely sure I know what you mean with:

[quote]try to not couple presentation with logic, the game playfield size shouldn’t be linked to the size of your window(the logic shouldn’t worry about how big the window is, the window should worry about how big the game wants to be)
[/quote]
And I have plans for making a Screen class for handling rendering. :slight_smile: