Java Slick2D: When camera is scrolling, character moves too fast

I’ve been playing around with Slick2D for Java and I managed to get it to display maps and have my character sprite move around.

I’ve tried to implement a camera that follows the player so that the map scrolls. While the map is scrolling, that characters move speed is faster than it should be (possibly due to the camera srolling it as well as it moving with the keys)

I’m stumped on how to solve it though

This is an example of what i mean - https://www.youtube.com/watch?v=IPmw4t1YnUo

The camera code is something i found on the slick forums, and modified slightly to draw each layer seperatly, modifying both drawmap methods to add the layer in. I would ask on the forums but they seem dead.

This is the camera code

    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    package engine;
    
    /**
     *
     * @author Ceri
     */
    import java.awt.geom.Point2D;
    import org.newdawn.slick.GameContainer;
    import org.newdawn.slick.geom.Shape;
    import org.newdawn.slick.tiled.TiledMap;
    
    public class Camera {
    
        /**
         * the map used for our scene
         */
        protected TiledMap map;
    
        /**
         * the number of tiles in x-direction (width)
         */
        protected int numTilesX;
    
        /**
         * the number of tiles in y-direction (height)
         */
        protected int numTilesY;
    
        /**
         * the height of the map in pixel
         */
        protected int mapHeight;
    
        /**
         * the width of the map in pixel
         */
        protected int mapWidth;
    
        /**
         * the width of one tile of the map in pixel
         */
        protected int tileWidth;
    
        /**
         * the height of one tile of the map in pixel
         */
        protected int tileHeight;
    
        /**
         * the GameContainer, used for getting the size of the GameCanvas
         */
        protected GameContainer gc;
    
        /**
         * the x-position of our "camera" in pixel
         */
        protected float cameraX;
    
        /**
         * the y-position of our "camera" in pixel
         */
        protected float cameraY;
    
        protected Point2D.Float currentCenterPoint = new Point2D.Float(0, 0);
    
        /**
         * Create a new camera
         *
         * @param gc the GameContainer, used for getting the size of the GameCanvas
         * @param map the TiledMap used for the current scene
         */
        public Camera(GameContainer gc, TiledMap map) {
            this.map = map;
    
            this.numTilesX = map.getWidth();
            this.numTilesY = map.getHeight();
    
            this.tileWidth = map.getTileWidth();
            this.tileHeight = map.getTileHeight();
    
            this.mapWidth = this.numTilesX * this.tileWidth;
            this.mapHeight = this.numTilesY * this.tileHeight;
    
            this.gc = gc;
        }
    
        /**
         * "locks" the camera on the given coordinates. The camera tries to keep the
         * location in it's center.
         *
         * @param x the real x-coordinate (in pixel) which should be centered on the
         * screen
         * @param y the real y-coordinate (in pixel) which should be centered on the
         * screen
         * @return
         */
        public Point2D.Float centerOn(float x, float y) {
            //try to set the given position as center of the camera by default
            cameraX = x - gc.getWidth() / 2;
            cameraY = y - gc.getHeight() / 2;
    
            //if the camera is at the right or left edge lock it to prevent a black bar
            if (cameraX < 0) {
                cameraX = 0;
            }
            if (cameraX + gc.getWidth() > mapWidth) {
                cameraX = mapWidth - gc.getWidth();
            }
    
            //if the camera is at the top or bottom edge lock it to prevent a black bar
            if (cameraY < 0) {
                cameraY = 0;
            }
            if (cameraY + gc.getHeight() > mapHeight) {
                cameraY = mapHeight - gc.getHeight();
            }
    
            currentCenterPoint.setLocation(cameraX, cameraY);
            return currentCenterPoint;
        }
    
        /**
         * "locks" the camera on the center of the given Rectangle. The camera tries
         * to keep the location in it's center.
         *
         * @param x the x-coordinate (in pixel) of the top-left corner of the
         * rectangle
         * @param y the y-coordinate (in pixel) of the top-left corner of the
         * rectangle
         * @param height the height (in pixel) of the rectangle
         * @param width the width (in pixel) of the rectangle
         */
        public void centerOn(float x, float y, float height, float width) {
            this.centerOn(x + width / 2, y + height / 2);
        }
    
        /**
         * "locks the camera on the center of the given Shape. The camera tries to
         * keep the location in it's center.
         *
         * @param shape the Shape which should be centered on the screen
         */
        public void centerOn(Shape shape) {
            this.centerOn(shape.getCenterX(), shape.getCenterY());
        }
    
        /**
         * draws the part of the map which is currently focussed by the camera on
         * the screen
         */
        public void drawMap(int layer) {
            this.drawMap(0, 0, layer);
        }
    
        /**
         * draws the part of the map which is currently focussed by the camera on
         * the screen.

         * You need to draw something over the offset, to prevent the edge of the
         * map to be displayed below it

         * Has to be called before Camera.translateGraphics() !
         *
         * @param offsetX the x-coordinate (in pixel) where the camera should start
         * drawing the map at
         * @param offsetY the y-coordinate (in pixel) where the camera should start
         * drawing the map at
         */
        public void drawMap(int offsetX, int offsetY, int layer) {
            //calculate the offset to the next tile (needed by TiledMap.render())
            int tileOffsetX = (int) -(cameraX % tileWidth);
            int tileOffsetY = (int) -(cameraY % tileHeight);
    
            //calculate the index of the leftmost tile that is being displayed
            int tileIndexX = (int) (cameraX / tileWidth);
            int tileIndexY = (int) (cameraY / tileHeight);
    
            //finally draw the section of the map on the screen
            map.render(
                    tileOffsetX + offsetX,
                    tileOffsetY + offsetY,
                    tileIndexX,
                    tileIndexY,
                    (gc.getWidth() - tileOffsetX) / tileWidth + 1,
                    (gc.getHeight() - tileOffsetY) / tileHeight + 1, layer, false);
        }
    
        /**
         * Translates the Graphics-context to the coordinates of the map - now
         * everything can be drawn with it's NATURAL coordinates.
         */
        public void translateGraphics() {
            gc.getGraphics().translate(-cameraX, -cameraY);
        }
    
        /**
         * Reverses the Graphics-translation of Camera.translatesGraphics(). Call
         * this before drawing HUD-elements or the like
         */
        public void untranslateGraphics() {
            gc.getGraphics().translate(cameraX, cameraY);
        }
    
    }

and this is how its being called
In the engine class

    public void render(GameContainer gc, Graphics g) throws SlickException {
            
            camera = new Camera(gc, world.map);
            camera.centerOn(player.getX(), player.getY());
            camera.drawMap(0);
            camera.drawMap(1);
            player.draw();
            camera.drawMap(2);
        }

The problem is that you are moving both, the player and the camera, at the same time.
If the camera moves, the player must stop, and vice versa. Instead of using the player coordinates, use other variables and update the player or the camera depending on what must move.

So i need to stop one or the other from moving. Stopping the player is probably the easiest, i could probably check the players update and make it so that the change in x and y doesn’t happen by having it check that the camera isn’t scrolling , though i’ve yet to work out where the scrolling takes place in the camera class to set a variable that can be checked.

What Endos said; Basically you’re moving the character on the screen not on the map, then moving the map as well. The player isnt actually speeding up, he’s staying at the same speed… But you’re also moving the map at the same speed in the opposite direction, thus he ends up traveling twice as fast. (Think of it like this: He’s walking the same speed, but now on an escalator, so he’s covering 2x the ground)

A possible solution is to seperate the camera and player completely, what I do is render the player on the screen in relation to where the map is, and then make the map move in relation to where the player is. They don’t have any connection to each other.

(NOTE: Stripped all the way down to be super simple)
Example of renders/moving my character, where entityX and Y are in relation to where the map is being rendered, NOT in relation to where the character is on the screen. :


	public void render(Graphics g, boolean debug) throws SlickException{		
		entity.render(entityX+mapX,entityY+mapY);
	}

	public void moveEntityNorth() throws SlickException{
		entityY -= entitySpeed;
	}

and moving the map/camera around to follow the player, but it’s coordinates aren’t “attached” to the player. NOTE: You will have to change the values after >=/<= to match your screen size to where you want the movement to start. My game is rendered in 720p and scaled x3, thats why my values are so low. Yours will probably be much higher than mine:


	public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException{
		if((mapX*-1)-entityX >= -161){
			mapX += entitySpeed;
		}

		if((mapX*-1)-entityX <= -233){
			mapX -= entitySpeed;
		}
		
		if((mapY*-1)-entityY() >= -86){
			mapY += entitySpeed;
		}
		
		if((mapY*-1)-entityY() <= -106){
			mapY -= entitySpeed;
		}
	}

The results are a camera that follows around the player, not a camera that moves based on your inputs. If that makes sense.

(Note: This may also help solve your TileID problem in your other thread)