2
\$\begingroup\$

I originally created this question at stackoverflow but got no answers, so hopefully someone will be able to help me here.

So for the first time ever I'm making a html canvas game. So far I came up with a simple one dimensional array map and render it to the canvas using a sprite sheet. I also made sure I can move the sprite character on the screen.

Now that I have achieved that and I can move the character, I want to have a bigger array map and render only a smaller part of it on the canvas and as I'm moving my character, make the canvas move to the side and show the rest of the map.

I have read some stackoverflow questions related to that but I just can't understand the code and some math behind it. Even if I get it, the example codes are totally different than mine and I don't know how to implement that in my case.

So far I have 580x540 size canvas and such a map:

var mapArray = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
  [0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
  [0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ];

So first I want to understand, how can I make a big map and show only a portion of it on the canvas?

Now I'm looping through the whole array and drawing on every item in array. But if then how can I make sure that for example only half of the array is shown on the canvas ant the other half will be shown only on the scroll then character moves to the side?

Here is my code so far:

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var mapArray = [
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 2, 2, 0],
  [0, 0, 1, 1, 1, 0, 0, 2, 0, 0],
  [0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];

var StyleSheet = function(image, width, height) {
  this.image = image;
  this.width = width;
  this.height = height;

  this.draw = function(image, sx, sy, swidth, sheight, x, y, width, height) {
    context.drawImage(image, sx, sy, swidth, sheight, x, y, width, height);
  };
  this.drawimage = function(image, x, y, width, height) {
    context.drawImage(image, x, y, width, height);
  };
};


var viewport = {};
/* Initial Sprite Position */
viewport.width = canvas.width/2;
viewport.height = canvas.height/2;

var boatPosX = 230;
var boatPosY = 200;

function render(viewport) {

  requestAnimationFrame(render);

  for (let i = 0; i < mapArray.length; i++) {
    for (let j = 0; j < mapArray[i].length; j++) {
      if (mapArray[i][j] == 0) {
        this.sprite.draw(
          background,
          190,
          230,
          26,
          26,
          i * this.sprite.width,
          j * this.sprite.height,
          this.sprite.width,
          this.sprite.height
        );
      }
      if (mapArray[i][j] == 1) {
        this.sprite.draw(
          background,
          30,
          30,
          26,
          26,
          i * this.sprite.width,
          j * this.sprite.height,
          this.sprite.width,
          this.sprite.height
        );
      }
      if (mapArray[i][j] == 2) {
        this.sprite.draw(
          background,
          200,
          20,
          26,
          26,
          i * this.sprite.width,
          j * this.sprite.height,
          this.sprite.width,
          this.sprite.height
        );
      }
    }
  }
  this.ship.drawimage(boat, boatPosX, boatPosY, 50, 50);
};

function move(e) {
  if (e.keyCode == 39) {
    boatPosX += 2;
    console.log("right");
  }
  if (e.keyCode == 37) {
    boatPosX -= 2;
    console.log("left");
  }
}

document.onkeydown = move;

var background = new Image();
background.src = "ground.png";
var sprite = new StyleSheet(background, 36, 36);

var boat = new Image();
boat.src = "ship.png";
var ship = new StyleSheet(boat, 90, 100);

render();
\$\endgroup\$
  • \$\begingroup\$ So found this SO question stackoverflow.com/questions/16919601/… and it has some good answers, but the answers are using single image whereas in my case I'm using spritesheet tileset and array for map. That makes it still unclear how to achieve this \$\endgroup\$ – Limpuls May 15 '18 at 23:41
  • \$\begingroup\$ I will delete SO version myself. This is more game dev related question anyway. Sorry for the trouble \$\endgroup\$ – Limpuls May 15 '18 at 23:47
  • \$\begingroup\$ Hey, no worries :) \$\endgroup\$ – Vaillancourt May 15 '18 at 23:48
1
\$\begingroup\$

I recommend starting in 1 dimension to understand it. Let's say you have tiles that are 32 x 32 pixels, and you have a screen that's 320 x 32 (remember we're only taking about moving in 1 dimension, so only left and right in this example). And let's say you have 100 tiles across. So your world is 3200 x 32 pixels. So at any given moment, you'll be showing 1/10th of the world at 320 x 32.

To keep everything straight, you'll need to keep track of a pixel offset that tells you how far left or right you are in the world. It starts at 0. As the player moves to the right, it increases up to the maximum. In this case, that would be 3200 - 320 = 2880. When it's at 2880, it's showing the rightmost 320 pixels in your world.

To draw the portion of the world that's currently visible in your game, you need to calculate which tiles intersect the canvas. You can do that by doing an integer division of the offset by the tile width. So in this case, startTileIndex = offset / 32. That tells you which element in your array of tiles you need to start at. Since the canvas is 320 pixels wide, you'll need the next 10 tiles.

Now, that tells you which tile you need, but you also need to account for the fact that you may be offset by a partial tile. This value can be calculated by using the remainder of the offset divided by the tile width. In case it would be tileOffset = pixelOffset % 32 where the % operator is modulus division.

So you can loop over the tiles to draw the visible ones and place them at the right position:

startTile = pixelOffset / tileWidth;
tileOffset = pixelOffset % tileWidth;
numTiles = (canvasWidth / tileWidth) + 1;
for (tileIndex = 0; tileIndex < numTiles; tileIndex++)
{
    map [ startTileIndex + tileIndex ].drawAt(-tileOffset, 0, tileWidth, tileHeight);
}

To do this in 2 dimensions, you need to keep track of both an x and y pixel offset, and you need to index into a 2D array for your map. You calculate the indexes and offsets in a similar manner for both the x and y dimensions.

\$\endgroup\$
1
\$\begingroup\$

The easiest way to implement this is to loop through the map and draw the images (like you're doing), but offset everything by a certain value so that your main character will stay in the middle.

Your code for drawing the images is looking pretty good and it looks you want the ship to be in the center. To have the visible part of the screen follow the ship, you will need to calculate a display offset so that this ship will always be in the center of the screen.

Let's say the ship is stored in x and y and the screen size is stored in width and height. The offset would be want to put the ship at (width/2, height/2), but it's being displayed at (x, y) so the offset would be (width/2 - x, height/2 - y).

When I was making The World of Engineers, I had way too many images being displayed at once, and this was creating lots of lag (especially when I was trying to manage collision). My solution to this problem was to first check if a sprite will even show on the screen, then draw and update it. This way there aren't a bunch of sprites being drawn that have no effect on the game.

EDIT:

I forgot to explain this, but you would display everything the same, except the x and y values for each thing should be translated by (offsetX, offsetY).

For the player, you would draw it at (player.x + offsetX, player.y + offsetY), and for the background images, you would draw it at (image.x + offsetX, image.y + offsetY)

\$\endgroup\$
  • \$\begingroup\$ Can you explain what you mean by calculate a display offset? I already changed my code to have the ship in the center var boatPosX = canvas.width/2 - 50; var boatPosY = canvas.height/2 - 50; using your given formula and made much bigger array so that it would not all fit in the 580x540 canvas. All good so far but I don't understand how I drag my canvas to the side as I press arrow key to follow my character or just scroll to the side and give the illusion of the characrer moving. \$\endgroup\$ – Limpuls May 16 '18 at 20:01
  • \$\begingroup\$ I added an edit, but you don't actually "drag" the canvas, you change where all the sprites are being drawn by the same amount. \$\endgroup\$ – Pranav Patil May 16 '18 at 21:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.