2
\$\begingroup\$

I have just implemented a 2D player camera for my game, everything works properly except the player on the screen jitters when it moves between tiles. What I mean by jitter, is that if the player is moving the camera updates the tileset to be drawn and if the player steps to the right, the camera snaps that way. The movement is not smooth.

I'm guessing this is occurring because of how I implemented the function to calculate the current viewable area or how my draw function works.

I'm not entirely sure how to fix this. This camera system was entirely of my own creation and a first attempt at that, so it's very possible this is not a great way of doing things.

My camera class, pulls information from the current tileset and calculates the viewable area. Right now I am targeting a resolution of 800 by 600. So I try to fit the appropriate amount of tiles for that resolution. My camera class, after calculating the current viewable tileset relative to the players location, returns a slice of the original tileset to be drawn. This tileset slice is updated every frame according to the players position.

This slice is then passed to the map class, which draws the tile on screen.

//Map Draw Function
//This draw function currently matches the GID of the tile to it's location on the
//PNG file of the tileset and then draws this portion on the screen 
void Draw(SDL_Surface* background, int[] _tileSet)
{
  enforce( tilesetImage != null, "Tileset is null!");
  enforce( background != null, "BackGround is null!");
  int i = 0;
  int j = 0;
  SDL_Rect DestR, SrcR;
  
  SrcR.x = 0;
  SrcR.y = 0;
  SrcR.h = 32;
  SrcR.w = 32;

  foreach(tile; _tileSet)
  {
                      //This code is matching the current tiles ID to the tileset image
    SrcR.x = cast(short)(tileWidth * (tile >= 11 ? (tile - ((tile / 10) * 10) - 1)  : tile - 1)); 
    SrcR.y = cast(short)(tileHeight * (tile > 10 ? (tile / 10) : 0));
                      
                       //Applying the tile to the surface
    SDL_BlitSurface( tilesetImage, &SrcR, background, &DestR );
                      
                        //this keeps track of what column/row we are on
    i++;
    if ( i == mapWidth )
    {
      i = 0;
      j++;
    }
    DestR.x = cast(short)(i * tileWidth);
    DestR.y = cast(short)(j * tileHeight);

  }
}

Camera Class

class Camera
{
private:
  //A rectangle representing the view area
  SDL_Rect viewArea;      
  
  //In number of tiles
  int viewAreaWidth;      
  int viewAreaHeight;      
  
  //This is the x and y coordinate of the camera in MAP SPACE IN PIXELS
  vect2 cameraCoordinates;  
  
  //The player location in map space IN PIXELS
  vect2 playerLocation;    

  //This is the players location in screen space;
  vect2 playerScreenLoc;    
  
  int playerTileCol;
  int playerTileRow;

  int cameraTileCol;
  int cameraTileRow;

  //The map is stored in a single array with the tile ids
  //this corresponds to the  index of the starting and ending tile
  int cameraStartTile, cameraEndTile; 

  //This is a slice of the current tile set
  int[] tileSetCopy;

  int mapWidth;
  int mapHeight;

  int tileWidth;
  int tileHeight;
  
public:
  this()
  {
    this.viewAreaWidth    = 25;
    this.viewAreaHeight    = 19;

    this.cameraCoordinates  = vect2(0, 0);
    this.playerLocation    = vect2(0, 0);

    this.viewArea = SDL_Rect (0, 0, 0, 0);

    this.tileWidth = 32;
    this.tileHeight = 32;
  }
  
  void Init(vect2 playerPosition, ref int[] tileSet, int mapWidth, int mapHeight )
  {  
    playerLocation = playerPosition;
    this.mapWidth = mapWidth;
    this.mapHeight = mapHeight;

    CalculateCurrentCameraPosition( tileSet, playerPosition );

    //writeln( "Tile Set Copy: ", tileSetCopy );
    //writeln( "Orginal Tile Set: ", tileSet );
  }

  void CalculateCurrentCameraPosition( ref int[] tileSet, vect2 playerPosition )
  {
    playerLocation = playerPosition;

    playerTileCol = cast(int)((playerLocation.x / tileWidth) + 1);
    playerTileRow = cast(int)((playerLocation.y / tileHeight) + 1);

    //writeln( "Player Tile (Column, Row): ","(", playerTileCol, ", ", playerTileRow, ")");

    cameraTileCol = playerTileCol - (viewAreaWidth / 2);
    cameraTileRow = playerTileRow - (viewAreaHeight / 2);

    CameraMapBoundsCheck();
    
    //writeln( "Camera Tile Start (Column, Row): ","(", cameraTileCol, ", ", cameraTileRow, ")");
    
    cameraStartTile = ( (cameraTileRow - 1) * mapWidth ) + cameraTileCol - 1;

    //writeln( "Camera Start Tile: ", cameraStartTile );

    cameraEndTile = cameraStartTile + ( viewAreaWidth * viewAreaHeight ) * 2;

    //writeln( "Camera End Tile: ", cameraEndTile );
    
    tileSetCopy =  tileSet[cameraStartTile..cameraEndTile];
  }

  vect2 CalculatePlayerScreenLocation()
  {
    cameraCoordinates.x = cast(float)(cameraTileCol * tileWidth);
    cameraCoordinates.y = cast(float)(cameraTileRow * tileHeight);

    playerScreenLoc = playerLocation - cameraCoordinates + vect2(32, 32);;

    //writeln( "Camera Coordinates: ", cameraCoordinates );
    //writeln( "Player Location (Map Space): ", playerLocation );
    //writeln( "Player Location (Screen Space): ", playerScreenLoc );

    return playerScreenLoc;
  }
  
  void CameraMapBoundsCheck()
  {
    if( cameraTileCol < 1 )
      cameraTileCol = 1;

    if( cameraTileRow < 1 )
      cameraTileRow = 1;

    if( cameraTileCol + 24 > mapWidth )
      cameraTileCol = mapWidth - 24;

    if( cameraTileRow + 19 > mapHeight )
      cameraTileRow = mapHeight - 19;
  }

  ref int[] GetTileSet()
  {
    return tileSetCopy;
  }

  int GetViewWidth()
  {
    return viewAreaWidth;
  }
}

Along with the Palindromes helpful response, I found that my bug was because I was calculating the camera coordinates in terms of tiles instead of pixels which caused the irregular motion.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

lazyfoo has some really great tutorials, and this is what I found:

If dot is whatever you are drawing:

apply_surface( x - camera.x, y - camera.y, dot, screen );

where apply_surface blits to the SDL apply_surface thingy...

Really, the camera is just an SDL_Rect with height being the screen height, width being screen width, and x and y just being the offsets...

This is the code from the scrolling tutorial, I didn't make this!

//Center the camera over the dot
camera.x = ( x + DOT_WIDTH / 2 ) - SCREEN_WIDTH / 2;
camera.y = ( y + DOT_HEIGHT / 2 ) - SCREEN_HEIGHT / 2;

//Keep the camera in bounds.
if( camera.x < 0 )
{
    camera.x = 0;    
}
if( camera.y < 0 )
{
    camera.y = 0;    
}
if( camera.x > LEVEL_WIDTH - camera.w )
{
    camera.x = LEVEL_WIDTH - camera.w;    
}
if( camera.y > LEVEL_HEIGHT - camera.h )
{
    camera.y = LEVEL_HEIGHT - camera.h;    
}  

simple stuff, isn't it?

If you ask me, the SDL_Rect idea is much better than having a class, and it is incredibly smooth.

if you want something that 'doesn't move with the camera' i.e. a house, just do this:

apply_surface(x,y,sprite,screen, &camera);

where the extra function is calls this:

SDL_BlitSurface( source, clip, destination, &offset );

and the function definition is this:

apply_surface( int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect *clip)
\$\endgroup\$
3
  • \$\begingroup\$ Thanks. I'll take a look at it. This does seem a lot more simple than my method. I'll go take a look at lazy foo. \$\endgroup\$
    – RedShft
    Mar 25, 2012 at 1:30
  • \$\begingroup\$ Ok, what I tried doing is subtracting the area i'm drawing in by the camera coordinates(in map space). This doesn't solve my problem though. I bet it has to do with my draw function, but the screen keeps jerking one tile over in the direction i'm moving. \$\endgroup\$
    – RedShft
    Mar 25, 2012 at 2:29
  • \$\begingroup\$ OK, glad I could help, I'm still kind of learning, but feel free to ask me for any help! \$\endgroup\$ Mar 25, 2012 at 4:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .