1
\$\begingroup\$

I'm doing a 2D Tile Game in Java. It should be the right calculations to make the camera following the player only if it is located at a distance from the center of the window, but this only works the first time the player overcame it, not all of them later. (I'm trying to do it like the snippet)

What is my mistake? Can anyone help me please?

// all this code is in the game loop
int cx = width/2;
int cy = height/2;

int dx = Math.abs(player.x - cx);
int dy = Math.abs(player.y - cy);

int xoff = 0;
int yoff = 0;

if(dx > 100){
    if(dx < player.x){
        xoff += player.speed;
    }else{
        xoff -= player.speed;
    }
}
if(dy > 100){
    if(dy < player.y){
        yoff += player.speed;
    }else{
        yoff -= player.speed;
    }
}

level.render(xoff, yoff, screen);

const RUN_SPEED = 5.5;

  var camPanX = 0.0;
  var camPanY = 0.0;
  const PLAYER_DIST_FROM_CENTER_BEFORE_CAMERA_PAN_X = 150;
  const PLAYER_DIST_FROM_CENTER_BEFORE_CAMERA_PAN_Y = 100;

  const KEY_LEFT_ARROW = 37;
  const KEY_UP_ARROW = 38;
  const KEY_RIGHT_ARROW = 39;
  const KEY_DOWN_ARROW = 40;
  var holdLeft = false;
  var holdRight = false;
  var holdUp = false;
  var holdDown = false;
  
  function initInput() {
    document.addEventListener("keydown", keyPressed);
    document.addEventListener("keyup", keyReleased);
  }
  
  function setKeyHoldState(thisKey, setTo) {
    if(thisKey == KEY_LEFT_ARROW) {
      holdLeft = setTo;
    }
    if(thisKey == KEY_RIGHT_ARROW) {
      holdRight = setTo;
    }
    if(thisKey == KEY_UP_ARROW) {
      holdUp = setTo;
    }
    if(thisKey == KEY_DOWN_ARROW) {
      holdDown = setTo;
    }
  }
  
  function keyPressed(evt) {
    setKeyHoldState(evt.keyCode, true);
    evt.preventDefault(); // without this, arrow keys scroll the browser!
  }
  
  function keyReleased(evt) {
    setKeyHoldState(evt.keyCode, false);
  }

  const BRICK_W = 60;
  const BRICK_H = 60;
  const BRICK_GAP = 1;
  const BRICK_COLS = 20;
  const BRICK_ROWS = 15;
  var brickGrid =
      [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
        1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
        1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1,
        1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
        1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1,
        1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1,
        1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1,
        1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
        1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1,
        1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1,
        1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1,
        1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];;
  
  var canvas, canvasContext;
  
  function brickTileToIndex(tileCol, tileRow) {
    return (tileCol + BRICK_COLS*tileRow);
  }

  function isBrickAtTileCoord(brickTileCol, brickTileRow) {
    var brickIndex = brickTileToIndex(brickTileCol, brickTileRow);
    return (brickGrid[brickIndex] == 1);
  }
  
  function isBrickAtPixelCoord(hitPixelX, hitPixelY) {
    var tileCol = hitPixelX / BRICK_W;
    var tileRow = hitPixelY / BRICK_H;
    
    // using Math.floor to round down to the nearest whole number
    tileCol = Math.floor( tileCol );
    tileRow = Math.floor( tileRow );

    // first check whether the slider is within any part of the brick wall
    if(tileCol < 0 || tileCol >= BRICK_COLS ||
       tileRow < 0 || tileRow >= BRICK_ROWS) {
       return false;
    }
    
    var brickIndex = brickTileToIndex(tileCol, tileRow);
    return (brickGrid[brickIndex] == 1);
  }
  
  function sliderMove() {
    var nextX = sliderX;
    var nextY = sliderY;

    if(holdLeft) {
      nextX += -RUN_SPEED;
    }
    if(holdRight) {
      nextX += RUN_SPEED;
    }
    if(holdUp) {
      nextY += -RUN_SPEED;
    }
    if(holdDown) {
      nextY += RUN_SPEED;
    }

    if(isBrickAtPixelCoord(nextX,nextY) == false) {
      sliderX = nextX;
      sliderY = nextY;
    }
  }

  window.onload = function() {
    canvas = document.getElementById('gameCanvas');
    canvasContext = canvas.getContext('2d');
    
    initInput();
    
    // these next few lines set up our game logic and render to happen 30 times per second
    var framesPerSecond = 30;
    setInterval(function() {
        moveEverything();
        drawEverything();
      }, 1000/framesPerSecond);
      
    sliderReset();
  }
  
  function sliderReset() {
    // center slider on screen
    sliderX = canvas.width/2;
    sliderY = canvas.height/2;
  }

  function instantCamFollow() {
    camPanX = sliderX - canvas.width/2;
    camPanY = sliderY - canvas.height/2;
  }

  function cameraFollow() {
    var cameraFocusCenterX = camPanX + canvas.width/2;
    var cameraFocusCenterY = camPanY + canvas.height/2;

    var playerDistFromCameraFocusX = Math.abs(sliderX-cameraFocusCenterX);
    var playerDistFromCameraFocusY = Math.abs(sliderY-cameraFocusCenterY);

    if(playerDistFromCameraFocusX > PLAYER_DIST_FROM_CENTER_BEFORE_CAMERA_PAN_X) {
      if(cameraFocusCenterX < sliderX)  {
        camPanX += RUN_SPEED;
      } else {
        camPanX -= RUN_SPEED;
      }
    }
    if(playerDistFromCameraFocusY > PLAYER_DIST_FROM_CENTER_BEFORE_CAMERA_PAN_Y) {
      if(cameraFocusCenterY < sliderY)  {
        camPanY += RUN_SPEED;
      } else {
        camPanY -= RUN_SPEED;
      }
    }

    // instantCamFollow();

    // this next code blocks the game from showing out of bounds
    // (this isn't required, if you don't mind seeing beyond edges)
    if(camPanX < 0) {
      camPanX = 0;
    }
    if(camPanY < 0) {
      camPanY = 0;
    }
    var maxPanRight = BRICK_COLS * BRICK_W - canvas.width;
    var maxPanTop = BRICK_ROWS * BRICK_H - canvas.height;
    if(camPanX > maxPanRight) {
      camPanX = maxPanRight;
    }
    if(camPanY > maxPanTop) {
      camPanY = maxPanTop;
    }
  }
  
  function moveEverything() {
    sliderMove();
    cameraFollow();
  }
  
  function colorRect(topLeftX, topLeftY, boxWidth, boxHeight, fillColor) {
    canvasContext.fillStyle = fillColor;
    canvasContext.fillRect(topLeftX, topLeftY, boxWidth, boxHeight);
  }
  
  function colorCircle(centerX, centerY, radius, fillColor) {
    canvasContext.fillStyle = fillColor;
    canvasContext.beginPath();
    canvasContext.arc(centerX, centerY, radius, 0, Math.PI*2, true);
    canvasContext.fill();
  }
  
  function drawBricks() {
    for(var eachCol=0; eachCol<BRICK_COLS; eachCol++) { // in each column...
      for(var eachRow=0; eachRow<BRICK_ROWS; eachRow++) { // in each row within that col
      
        if( isBrickAtTileCoord(eachCol, eachRow) ) {
          var brickLeftEdgeX = eachCol * BRICK_W;
          var brickTopEdgeY = eachRow * BRICK_H;
          colorRect(brickLeftEdgeX, brickTopEdgeY,
                   BRICK_W - BRICK_GAP, BRICK_H - BRICK_GAP, 'blue' );
        } // end of isBrickAtTileCoord()
      } // end of for eachRow
    } // end of for eachCol
  } // end of drawBricks()

  function drawOnlyBricksOnScreen() {
    // what are the top-left most col and row visible on canvas?
    var cameraLeftMostCol = Math.floor(camPanX / BRICK_W);
    var cameraTopMostRow = Math.floor(camPanY / BRICK_H);

    // how many columns and rows of tiles fit on one screenful of area?
    var colsThatFitOnScreen = Math.floor(canvas.width / BRICK_W);
    var rowsThatFitOnScreen = Math.floor(canvas.height / BRICK_H);

    // finding the rightmost and bottommost tiles to draw.
    // the +1 and + 2 on each pushes the new tile popping in off visible area
    // +2 for columns since BRICK_W doesn't divide evenly into canvas.width
    var cameraRightMostCol = cameraLeftMostCol + colsThatFitOnScreen + 2;
    var cameraBottomMostRow = cameraTopMostRow + rowsThatFitOnScreen + 1;
    
    for(var eachCol=cameraLeftMostCol; eachCol<cameraRightMostCol; eachCol++) {
      for(var eachRow=cameraTopMostRow; eachRow<cameraBottomMostRow; eachRow++) {
      
        if( isBrickAtTileCoord(eachCol, eachRow) ) {
          var brickLeftEdgeX = eachCol * BRICK_W;
          var brickTopEdgeY = eachRow * BRICK_H;
          colorRect(brickLeftEdgeX, brickTopEdgeY,
                   BRICK_W - BRICK_GAP, BRICK_H - BRICK_GAP, 'blue' );
        } // end of isBrickAtTileCoord()
      } // end of for eachRow
    } // end of for eachCol
  } // end of drawBricks()
  
  function drawEverything() {
    // drawing black to erase previous frame, doing before .translate() since
    // its coordinates are not supposed to scroll when the camera view does
    colorRect(0, 0, canvas.width, canvas.height, 'black');

    canvasContext.save(); // needed to undo this .translate() used for scroll

    // this next line is like subtracting camPanX and camPanY from every
    // canvasContext draw operation up until we call canvasContext.restore
    // this way we can just draw them at their "actual" position coordinates
    canvasContext.translate(-camPanX,-camPanY);

    //drawBricks();
    drawOnlyBricksOnScreen();
    
    colorCircle(sliderX, sliderY, 10, 'white');

    canvasContext.restore(); // undoes the .translate() used for cam scroll

    // doing this after .restore() so it won't scroll with the camera pan
    canvasContext.fillStyle = 'white';
    canvasContext.fillText("Arrow keys to slide, scrolling demo",8,14);
  }
<canvas id="gameCanvas" width="800" height="600"></canvas>

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

I happen to be making a 2D game with a camera that follows the player right now. My camera is quite a bit more sophisticated than yours. The movement of the camera is smooth and springy. Anyway, here's your code

int cx = width/2;
int cy = height/2;

int dx = Math.abs(player.x - cx);
int dy = Math.abs(player.y - cy);

int xoff = 0;
int yoff = 0;

if(dx > 100){
    if(dx < player.x){
        xoff += player.speed;
    }else{
        xoff -= player.speed;
    }
}
if(dy > 100){
    if(dy < player.y){
        yoff += player.speed;
    }else{
        yoff -= player.speed;
    }
}

level.render(xoff, yoff, screen);

I'm guessing that off is added to all the positions of the game objects before they are rendered and c is the center of the window. d is the distance the player is from the center of the screen. You want to move off when the distance is less than 100 to keep the player on the screen. To do that you might want to do something like this.

const int centerRadius = 100;

int dx = player.x - cx;
int dy = player.y - cy;

if (Math.abs(dx) > centerRadius) {
  xoff -= dx > 0 ? dx - centerRadius : dx + centerRadius;
}
if (Math.abs(dy) > centerRadius) {
  yoff -= dy > 0 ? dy - centerRadius : dy + centerRadius;
}

Here I'm calculating the distance the player is from the edge of the square in the center of the screen. Then I'm moving the camera by that distance to keep the player within the rectangle.

This kind of code is very easy to get wrong and I'm not able to properly test my code. You might need to flip the signs somewhere to this to work right. If you want a more complicated camera, I'd be happy to help you out.

\$\endgroup\$
5
  • \$\begingroup\$ Thank you very much! But there are some problems. Some times, when the player is near to the bottom-right angle of the maximum radius, the xoffset is not calculated anymore, so the player can go off screen. And other times while the player is walking, the camera change completely posotion, and the player isn't visible any more. I dont know if thoose are problem of my code or the camera code. \$\endgroup\$ Aug 30, 2017 at 8:12
  • \$\begingroup\$ I see the mistake. It might be due to the else. I've edited the answer \$\endgroup\$ Aug 30, 2017 at 8:14
  • \$\begingroup\$ If you're happy with the results you can accept my answer. (Click on the ✅) \$\endgroup\$ Aug 30, 2017 at 8:22
  • \$\begingroup\$ scuse me but I found only now my last issue. When the player move, and the camera move,and the player remains in the last side of the maximum-radius touched,(he can move, but his position always the same compared to the map), and only when the player returns in his starting position then he can moves without the camera movement. \$\endgroup\$ Aug 30, 2017 at 9:10
  • \$\begingroup\$ @GiuseppeRomeo I understand. When the player moves, the camera will move to keep the player on the edge of the square. This means the player doesn't move relative to the screen while on the edge of the square. That's what your sample code does and that's what you asked for so that's what I gave you. \$\endgroup\$ Aug 30, 2017 at 22:17

You must log in to answer this question.

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