Camera class in top view

I’m developing a top-down-view RPG-something game… Like Zelda :persecutioncomplex:
I’m having my screen set to some absolute size (400x400 i think), and my “rooms” are the same size.
Each room is a map, but I dont want it excactly that way.

I see alot of you use a camera class to do this:
Scroll the screen with the camera, so I can make my rooms greater than just the frame…

Question is: how?
Can anyone give a detailed explanation on how it works? :o

Mads :slight_smile:

I have a Camera class that basically:

Divides screenwidth /2 and subtracts the player position (minutes half of players sprite width) and move screen by that.
Same with height.

A concrete example. If screen is 640 wide and player is at 300 and player is 32 pixels wide.
640/2 = 320 [screen width /2] - 300 [player [pos] = 20 - 16 [sprite width /2] =4

that means I shift the map over by 4 pixels.

Basically, whatever the player is, the camera is centered on him. If he moves left 32 pixels, the camera follows him over 32 pixels.

Or another way to look at it, the player is always perfectly centered in middle of the screen and map moves under him.

I like this approach because it isn’t dependent on resolution. The higher the resolution just means the player sees more of the map.
Maps can be as big or small as you like.

What? Weird post… Clicked the wrong button.

@Topic:
I might be slow, and it is late. Why do you divide by two?
I dont quite understand the drawing, and imput part of your example :L

How should I approach placing buildings in a such environment? A different coordinate system?
^How? :o

I might be slow, and it is late. Why do you divide by two?

To get the center point.

If you have a piece of paper that is 10 inches wide, if you fold it in half (that is divide by 2) you get the center point of paper.
Same with screen width, but measured in pixels.

Also depends on how you draw sprites. When I draw them at x and y it draws 0,0 of the sprites at that location.
If that is the case, you have to divide your sprite image by 2 to get center of image. If not, your sprite won’t be true center, it’ll be off by a bit.

What coordinate system are you using? Either pixels or tiles (like a tiled map) will work with this.
Not sure what other type of cord system you’d be using other than one of those?

This should help you out
http://www.javacooperation.gmxhome.de/ScrollingEng.html

@Dime
Thanks, I got the idea of centering my character now. I’m using a pixel map.
Before I just used the java graphics coords to define where my entities in game were, but I cant do that now.
That leads to the question - how do I approach placing my entities now?

@Swattkidd
Thank you very much, I found it very useful.

I’m still not sure how to define positions for in-game-objects, since I can’t use the graphics coordinates for that anymore :-\

nothing changes, you still place it all the same.

you character is still at 700 700 or whatever, but everything is moved.

so basically all taht changes is how you paint stuff, and thats only by a little bit :slight_smile: (if u do it right that is, hahah)

@H3ckboy
I don’t know if it is as simple as that, unfortunately. All my logic (as of now) relies on the painting coordinates.
For instance, I have object teleporting my character to a different map. That activates once my character reaches the paint-coords of the teleport-object.
That makes me think of having a different coordinate system, that is not the painting coords :-\

Does that make any sence? :stuck_out_tongue: …I don’t like to realize it, but I might have built a semi-fucked up engine :persecutioncomplex:

You really don’t need to get complicated.

I just have a scrollX and scrollY float, and when I draw the scene I draw every image at xPos+scrollX, yPos+scrollY. Or even simpler just translate your graphics context (in Graphics2D) or translate the transformation matrix (in OpenGL).

Then increment of decrement the scroll values based on where the player is, or even just scroll the screen to match how much the player moves, whenever they move.

The key thing to remember is that you’re only drawing things different, the actual positions of each object should not be changed.

Really simple.

I’m so annoyed at myself right now. I have tried, and tried, and tried to get this to work just with functionality for one direction. It doesn’t work :’(

I know I look so helpless when I ask, but can one of you please, please, provide just functionality for one direction? I’d be ever so grateful.
I just need to see this implemented simply :-\

In my update():


	screenWidth = gc.getWidth();
		screenHeight = gc.getHeight();
		
				
		if (followPlayer) {
			playerTileOffsetX = player.getTileOffset().x;
			playerTileOffsetY = player.getTileOffset().y;
			playerX = screenWidth/2- player.getCordPosition().x-16 - playerTileOffsetX;
			playerY = screenHeight/2 - player.getCordPosition().y-16 - playerTileOffsetY;
		}

in render (I use slick)


	defaultBackground.draw(0,0, gc.getWidth(), gc.getHeight());
		
		// If we're following player
		if (followPlayer) {
			g.translate(playerX, playerY);
		}

PlayerTileOffsetXY doesn’t matter. It’s just player offset in pixels from tiles. This lets it “smooth scroll” 1 pixel at a time instead of jumping 32 (it gets jerky if you jump to many pixels to quick).

Translate just shifts to screen: http://slick.cokeandcode.com/javadoc/org/newdawn/slick/Graphics.html#translate(float,%20float)

Then on top of the above, I just draw player in center of screen. Non translated.

This page should be helpful http://fivedots.coe.psu.ac.th/~ad/jg/ Chapter 12 should answer any questions you have about 2d scrolling.

I decided to draw my character in the middle, and then draw all other object at the graphical point += Scroll.
Then I increase/decrease the scroll upon keypress.

Two problems rised: First of all, the centering of the character isn’t working.
It looks like this:


g2d.drawImage(craft.getImage(), Scroll.WIDTH_1/2-48/2, Scroll.HEIGHT_1/2-48/2, this);

Height_1 and width_1, are my frames variables. My character-sprite is 48 pixels high, and wide.
It draws like this:

http://img337.imageshack.us/img337/5184/pictureut.png

Oh, yes… I used a sprite from minecraft for the example…

The second problem is that the Scroll variable isn’t changing correctly when the key is held down. There is a small delay, it goes like this upon keypress:
Change +1, delay, change that is constantly changing in the right direction.
What bothers me is the delay.
Here is my logic for that:
Board(canvas):


    public void actionPerformed(ActionEvent e) {
        craft.move();
        repaint();
    }


    private class TAdapter extends KeyAdapter {

        public void keyReleased(KeyEvent e) {
            craft.keyReleased(e);
        }

        public void keyPressed(KeyEvent e) {
            craft.keyPressed(e);
        }
    }

Craft (entity):


public void keyPressed(KeyEvent e) {

        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) {
            dx = -1;
            Board.ScrollX += -1;
        }

        if (key == KeyEvent.VK_RIGHT) {
            dx = 1;
            Board.ScrollX += 1;
        }

        if (key == KeyEvent.VK_UP) {
            dy = -1;
            Board.ScrollY += -1;
        }

        if (key == KeyEvent.VK_DOWN) {
            dy = 1;
            Board.ScrollY += 1;
        }
    }

    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();

        if (key == KeyEvent.VK_LEFT) {
            dx = 0;
            //Board.ScrollX = 0;
        }

        if (key == KeyEvent.VK_RIGHT) {
            dx = 0;
            //Board.ScrollX = 0;
        }

        if (key == KeyEvent.VK_UP) {
            dy = 0;
            //Board.ScrollY = 0;
        }

        if (key == KeyEvent.VK_DOWN) {
            dy = 0;
            //Board.ScrollY = 0;
        }
    }
public void move() {
        x += dx;
        y += dy;
    }

What am I doing wrong? :-\

Probably your issue is that you’re incrementing the scroll from within the key update, which means your scrolling is updating whenever the EDT is updating - the result is that it will look choppy and crap.

The solution is to store buttons that have been pressed in a list, then remove them from the list when they’ve been released. Factor all buttons pressed every update. This is good practice in general - you want absolutely everything that actually updates your entities to happen within the update function. Just like you don’t draw anything outside of the draw function, don’t update anything outside of the update function.

I made a quick scrolling demo for you. It’s just my fixed timestep demo with some scrolling thrown on top. Press Start to have the ball bounce around, then scroll with the keyboard keys. The ball’s position never changes from the scrolling - things are merely drawn offset from the scroll amount. If you’re confused from the interpolation, that’s just what to do to get an FPS higher than your game hertz.


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class ScrollTest extends JFrame implements ActionListener, KeyListener
{
	private GamePanel gamePanel = new GamePanel();
	private JButton startButton = new JButton("Start");
	private JButton quitButton = new JButton("Quit");
	private boolean running = false;
	private int fps = 60;
	private int frameCount = 0;
	
	private ArrayList<Integer> keysPressed;
	
	public ScrollTest()
	{
		super("Fixed Timestep Game Loop Test");
		Container cp = getContentPane();
		cp.setLayout(new BorderLayout());
		JPanel p = new JPanel();
		p.setLayout(new GridLayout(1,2));
		p.add(startButton);
		p.add(quitButton);
		cp.add(gamePanel, BorderLayout.CENTER);
		cp.add(p, BorderLayout.SOUTH);
		setSize(500, 500);
		
		keysPressed = new ArrayList<Integer>();
		
		startButton.addActionListener(this);
		quitButton.addActionListener(this);
		gamePanel.addKeyListener(this);
	}
	
	public static void main(String[] args)
	{
		ScrollTest st = new ScrollTest();
		st.setVisible(true);
	}
	
	public void actionPerformed(ActionEvent e)
	{
		Object s = e.getSource();
		if (s == startButton)
		{
			running = !running;
			if (running)
			{
				startButton.setText("Stop");
				runGameLoop();
			}
			else
			{
				startButton.setText("Start");
			}
		}
		else if (s == quitButton)
		{
			System.exit(0);
		}
	}
	
	public void keyPressed(KeyEvent e) 
	{
		if (!keysPressed.contains(new Integer(e.getKeyCode())))
		{
			keysPressed.add(new Integer(e.getKeyCode()));
		}
	}
	public void keyReleased(KeyEvent e)
	{
		keysPressed.remove(new Integer(e.getKeyCode()));
	}
	public void keyTyped(KeyEvent e)
	{
		//Do nothing.
	}
	
	//Starts a new thread and runs the game loop in it.
	public void runGameLoop()
	{
		Thread loop = new Thread()
		{
			public void run()
			{
				gameLoop();
			}
		};
		loop.start();
	}
	
	//Only run this in another Thread!
	private void gameLoop()
	{
		//This value would probably be stored elsewhere.
		final double GAME_HERTZ = 30.0;
		//Calculate how many NS each frame should take for our target game hertz.
		final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
		//At the very most we will update the game this many times before a new render.
		final int MAX_UPDATES_BEFORE_RENDER = 5;
		//We will need the last update time.
		double lastUpdateTime = System.nanoTime();
		//Store the last time we rendered.
		double lastRenderTime = System.nanoTime();
		
		//If we are able to get as high as this FPS, don't render again.
		final double TARGET_FPS = 60;
		final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
		
		//Simple way of finding FPS.
		int lastSecondTime = (int) (lastUpdateTime / 1000000000);
		
		while (running)
		{
			double now = System.nanoTime();
			int updateCount = 0;
			
			//Do as many game updates as we need to, potentially playing catchup.
			while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
			{
				updateGame();
				lastUpdateTime += TIME_BETWEEN_UPDATES;
				updateCount++;
			}
			
			//If for some reason an update takes forever, we don't want to do an insane number of catchups.
			//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
			if (lastUpdateTime - now > TIME_BETWEEN_UPDATES)
			{
				lastUpdateTime = now - TIME_BETWEEN_UPDATES;
			}
			
			//Render. To do so, we need to calculate interpolation for a smooth render.
			float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
			drawGame(interpolation);
			lastRenderTime = now;
			
			//Update the frames we got.
			int thisSecond = (int) (lastUpdateTime / 1000000000);
			if (thisSecond > lastSecondTime)
			{
				fps = frameCount;
				frameCount = 0;
				lastSecondTime = thisSecond;
			}
			
			//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
			while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
			{
				Thread.yield();
				now = System.nanoTime();
			}
		}
	}
	
	private void updateGame()
	{
		gamePanel.factorKeyPresses(keysPressed);
		gamePanel.update();
	}
	
	private void drawGame(float interpolation)
	{
		gamePanel.setInterpolation(interpolation);
		gamePanel.repaint();
	}
	
	private class GamePanel extends JPanel
	{
		float interpolation;
		float ballX, ballY, lastBallX, lastBallY;
		int ballWidth, ballHeight;
		float ballXVel, ballYVel;
		float ballSpeed;
		float scrollX, scrollY, lastScrollX, lastScrollY;
		
		int lastDrawX, lastDrawY;
		
		public GamePanel()
		{
			ballX = lastBallX = 100;
			ballY = lastBallY = 100;
			ballWidth = 25;
			ballHeight = 25;
			ballSpeed = 25;
			ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
			ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
		}
		
		public void setInterpolation(float interp)
		{
			interpolation = interp;
		}
		
		public void update()
		{
			requestFocus();
			
			lastBallX = ballX;
			lastBallY = ballY;
			
			ballX += ballXVel;
			ballY += ballYVel;
			
			if (ballX + ballWidth/2 >= getWidth())
			{
				ballXVel *= -1;
				ballX = getWidth() - ballWidth/2;
				ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
			}
			else if (ballX - ballWidth/2 <= 0)
			{
				ballXVel *= -1;
				ballX = ballWidth/2;
			}
			
			if (ballY + ballHeight/2 >= getHeight())
			{
				ballYVel *= -1;
				ballY = getHeight() - ballHeight/2;
				ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
			}
			else if (ballY - ballHeight/2 <= 0)
			{
				ballYVel *= -1;
				ballY = ballHeight/2;
			}
		}
		
		public void paintComponent(Graphics g)
		{
			super.paintComponent(g);
			
			//Draw a box around the area so we can see scrolling.
			float thisXScroll = (scrollX - lastScrollX) * interpolation + lastScrollX;
			float thisYScroll = (scrollY - lastScrollY) * interpolation + lastScrollY;
			g.setColor(Color.BLACK);
			g.drawRect((int)scrollX, (int)scrollY, getWidth(), getHeight());
			
			//Draw the ball.
			g.setColor(Color.RED);
			int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2 + thisXScroll);
			int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2 + thisYScroll);
			g.fillOval(drawX, drawY, ballWidth, ballHeight);
			
			lastDrawX = drawX;
			lastDrawY = drawY;
			
			g.setColor(Color.BLACK);
			g.drawString("FPS: " + fps, 5, 10);
			
			frameCount++;
		}
		
		public void factorKeyPresses(ArrayList<Integer> presses)
		{
			lastScrollX = scrollX;
			lastScrollY = scrollY;
			for (int i = 0; i < presses.size(); i++)
			{
				int key = presses.get(i).intValue();
				
				if (key == KeyEvent.VK_LEFT)
				{
					scrollX -= 5;
				}
				else if (key == KeyEvent.VK_RIGHT)
				{
					scrollX += 5;
				}
				else if (key == KeyEvent.VK_UP)
				{
					scrollY -= 5;
				}
				else if (key == KeyEvent.VK_DOWN)
				{
					scrollY += 5;
				}
			}
		}
	}
}

It’s actually not the EDTs fault. This is (intended and well thought through) OS behavior, try it by holding a key down in a textarea.

The suggested fix works almost everywhere: Linux (or Sun’s AWT for Linux) seems to forget some key-release events every now and then…

Actually, on a side note that is so true! I have the problem all the time. …I was beginning to think my computers keyboard was broke or something…

@Thread: I decided to let this be for now. I learned some important things with this, and I figuered I’d scratch all the work I have done now.
I need a proper way of dealing with games, because my regular loop (holding a steady 100ms loop, and doing things in the breaks without causing delay) didn’t seem to be enough. I need some more advanced timing…
I’ll have a look at slick when I feel for developing something again :stuck_out_tongue: :persecutioncomplex:

Thanks for the imformative answers guys.

That’s just wonderful. What do you do about these Linux people, then? Make press re-press keys to unstick them?

I guess it’s a moot point because I always use JInput in my games, but still…