Slick Forums

Discuss the Slick 2D Library
It is currently Thu May 23, 2013 7:32 pm

All times are UTC




Post new topic Reply to topic  [ 11 posts ] 
Author Message
PostPosted: Tue Aug 25, 2009 9:36 am 
Offline

Joined: Tue Aug 25, 2009 9:00 am
Posts: 5
I'm using Slick with TiledMaps for a 2D RPG.

Is there a easy way to implement scrolling with TiledMaps in the following way?

The player is displayed in the middle of the screen (like the Scroller-Demo with the tank), but as soon as the screen reaches the border of the map, the player position on the screen will start changing and the map is static now.

This and more ways of scrolling (look ahead while "running", ...) can be a real enhancement for Slick, because it's not really easy to code them :?

Has anybody already done something like this?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 25, 2009 7:08 pm 
Offline
Regular

Joined: Sun Dec 07, 2008 5:22 am
Posts: 238
Location: Vancouver, BC, Canada
I had the very same question when I started.
http://slick.javaunlimited.net/viewtopic.php?t=1416
The camera class Fletch described was just the ticket
Hope that helps.

_________________
If at first quads don't succeed tri tri again.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 26, 2009 11:17 am 
Offline

Joined: Tue Aug 25, 2009 9:00 am
Posts: 5
This is a great approach. Thank you very much :)

btw: this should be bundled with Slick for an easy integration with TiledMaps


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 26, 2009 2:07 pm 
Offline

Joined: Tue Aug 25, 2009 9:00 am
Posts: 5
I tried to implement it and got the following result. When walking, it scrolls smoothly, but the map is scrolling too fast and reaches the right edge (when walking to the right) way too soon. Additionally the more the player walks to the right, a growing black bar is at the right edge..

Here is my code:

Code:
//lock the camera on the player by default (player should be centered by the camera)
      cameraX = playerRealX - SCREEN_WIDTH / 2;
      cameraY = playerRealY - SCREEN_HEIGHT / 2;
      
      //if the camera reaches the left or right edge of the screen, lock it      
      if(cameraX < 0) cameraX = 0;
      if(cameraX > mapWidth - SCREEN_WIDTH) cameraX = mapWidth - SCREEN_WIDTH;
      
      //if the camera reaches the top or bottom edge of the screen, lock it      
      if(cameraY < 0) cameraY = 0;
      if(cameraY > mapHeight - SCREEN_HEIGHT) cameraY = mapHeight - SCREEN_HEIGHT;
      
      //calculate the player's drawing position on the screen
      playerScreenX = playerRealX - cameraX;
      playerScreenY = playerRealY - cameraY;
      
      //calculate the offset to the next tile (used by TiledMap.render())
      tileOffsetX = cameraX % TILESIZE;
      tileOffsetY = cameraY % TILESIZE;
      
      //calculate the position of the map by using the camera-position
      mapOffsetX = -cameraX;
      mapOffsetY = -cameraY;

      int tileIndexX = (int) (cameraX / TILESIZE);
      int tileIndexY = (int) (cameraY / TILESIZE);

      map.render(
            (int) (mapOffsetX - tileOffsetX),
            (int) (mapOffsetY - tileOffsetY),
            tileIndexX,
            tileIndexY,
            (SCREEN_WIDTH / TILESIZE) + 1,
            (SCREEN_HEIGHT / TILESIZE) + 1
      );


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 26, 2009 8:09 pm 
Offline
Regular

Joined: Sun Dec 07, 2008 5:22 am
Posts: 238
Location: Vancouver, BC, Canada
Generally its much easier to translate the graphics context and draw everything at its real position than it is to determine where everything ought to be. So get your cameraX and cameraY exactly as you are doing, but don't try to adjust the position of the player or map, rather;
Code:
public void render(Graphics g){
  //determine cameraX and cameraY
  g.translate(-cameraX, -cameraY);
  drawLayers(cameraX, cameraY, SCREEN_WIDTH, SCREEN_HEIGHT);
  //draw player at its real position
  g.translate(cameraX, cameraY);
}

//Draws only the necessary tiles for a given area
private void drawLayers(int x, int y, int w, int h, int[] layers){
    int tileOffsetX = (-1*x%mapTileSize_);
    int tileOffsetY = (-1*y%mapTileSize_);
    int tileIndexX  = x/mapTileSize_;
    int tileIndexY  = y/mapTileSize_;
   
    for(int i : layers){
      map.render(x + tileOffsetX,y+ tileOffsetY, tileIndexX, tileIndexY,
                    (w - tileOffsetX)/mapTileSize_ + 1,
                    (h - tileOffsetY)/mapTileSize_ + 1,
                    i, false);
    }
}

Basically if the camera is located at point 300,400 we shift the entire graphics context left 300 and up 400 and draw everything where it really is.

_________________
If at first quads don't succeed tri tri again.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 27, 2009 6:12 pm 
Offline

Joined: Tue Aug 25, 2009 9:00 am
Posts: 5
Thanks, the Graphics.translate() is really helpful. I now noticed my main bug: I was drawing the map with:

Code:
map.render(
            (int) (mapOffsetX - tileOffsetX),
            (int) (mapOffsetY - tileOffsetY),  ...


.. which was moving it to the left - correct would be:

Code:
map.render(-tileOffsetX, -tileOffsetY, ..


And for everybody that has the same problem, here my Camera class:

Use it in the following way:
Code:
//after calculating the positions of all entities
camera.centerOn(player.getX(), player.getY());

//in the render()-method
camera.drawMap();
camera.translateGraphics();

player.draw();
image.draw(realX, realY);


Code:
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;
   
   /**
    * 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.mapHeight = this.numTilesX * this.tileWidth;
      this.mapWidth = 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
    */
   public void 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();
   }
   
   /**
    * "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() {
      this.drawMap(0, 0);
   }
   
   /**
    * draws the part of the map which is currently focussed by the camera on the screen.<br>
    * You need to draw something over the offset, to prevent the edge of the map to be displayed below it<br>
    * 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) {
       //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);
   }
   
   /**
    * 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);
   }
   
}


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 31, 2009 2:22 pm 
Offline
User avatar

Joined: Wed Aug 26, 2009 2:33 pm
Posts: 36
Location: Melbourne, Australia
koopa wrote:
And for everybody that has the same problem, here my Camera class:

Use it in the following way:
Code:
//after calculating the positions of all entities
camera.centerOn(player.getX(), player.getY());

//in the render()-method
camera.drawMap();
camera.translateGraphics();

player.draw();
image.draw(realX, realY);

Give this man a medal! That is one incredibly handy class.

I copypasta'd it into my component framework I'm building up and it works flawlessly. However I was wrestling with whether the Camera class should be rendering the map. Before I had a World class that held, calculated and rendered the map - basically did what the Camera class does but far less elegantly.

I tried moving the camera-y parts into Camera and moving the rendering functionality of Camera into World, but soon realised that if I did that, Camera would be nothing but a container for cameraX and cameraY. Ultimately I moved the extra stuff (pretty much just kevin's tile map blocking mask and mask rendering function) from World into Camera and just scrapped World. Would there be a way to distribute the Camera class more evenly across the two classes like I was trying to do before?

Thanks again for the code - this should be required reading for anyone asking questions about tilemap scrolling ;)


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 31, 2009 6:24 pm 
Offline
Regular

Joined: Sun Dec 07, 2008 5:22 am
Posts: 238
Location: Vancouver, BC, Canada
Personally I don't have my camera class do any rendering. I let that be done by world what I do have is a camera.translateIn(Graphics) and a camera.translateOut(Graphics). So when my world needs to render I have something like:
Code:
public void render(Graphics g){
  camera.translateIn(g);
  //all rendering code
  camera.translateOut(g);
}
Basically my camera class does one thing decide where to draw what I'm drawing.
There are a number of reasons for not wanting the camera to do drawing. For starters when you have a bunch of object sprites in your world it means your camera class has to know about all of them in order to draw them. Also let's say you have a really large game world, you may want to break it up into a bunch of smaller maps so that you only need to keep the current tile map and any adjacent maps in memory. Now your camera class has to know about a potential N number of maps and all the entities that reside on each map. These sort of things make the class less cohesive.

There are a lot of other things you can do with the camera class though, for instance adding pan, zoom, and rotate methods that act as wrappers for the graphics.translate, rotate, and scale, can make them a bit easier to use. Also you can add a moveTo(int x, int y) method useful for cut scenes where the camera needs to slowly move to some other position of interest.

_________________
If at first quads don't succeed tri tri again.


Top
 Profile  
 
PostPosted: Fri Apr 06, 2012 3:13 pm 
Offline

Joined: Tue Mar 27, 2012 9:38 pm
Posts: 9
Bloody fantastic! That camera class works damn near perfectly! Thank you so much. Only thing I might want to do is somehow incorporate my actually rendering in another class, but maybe not.

Great! Thank you again!


Top
 Profile  
 
PostPosted: Mon Apr 16, 2012 2:17 pm 
Offline

Joined: Wed Mar 28, 2012 9:28 pm
Posts: 16
This a a great class and I'm already using it to scroll my tile maps.

I did find one isssue: At line 69/70 the code says

Code:
      this.mapHeight = this.numTilesX * this.tileWidth;
      this.mapWidth = this.numTilesY * this.tileHeight;


It should be
Code:
      this.mapWidth = this.numTilesX * this.tileWidth;
      this.mapHeight = this.numTilesY * this.tileHeight;


Also, I modified the class slightly by adding this instance variable
Code:
   protected Point2D.Float currentCenterPoint = new Point2D.Float(0,0);


and then changed the return value of the centerOn method to return the current camera x/y position
Code:
   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;
   }


Before I passed the modified x/y position back to my render method I could go all the way left by subtracting from my X location but I could continue to subtract from x while the map stops scrolling because the camera modified X and Y correctly but my render code wasn't. If I wanted to go to the right I'd add to X but my X variable wasn't being modified which the cameraX was so my map wouldn't scroll. By returning the Point2D.Float object I can modify the X/Y which I use to move the camera only once (in the centerOn method) and everything is sync'd up.


Top
 Profile  
 
PostPosted: Tue Jun 19, 2012 6:18 pm 
Offline

Joined: Tue Jun 19, 2012 6:13 pm
Posts: 1
This is very handy, and should actually be bumped up some :)
I'm new to developing in java and slick. I noticed when koopa's camera class that when the camera scrolls the animation of my player lag. This does not happen when I am close to the edges and the camera does not move. Anyone having a solution to this?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group