TiledMap Scrolling

3

I'm trying to build a camera system for a game that I plan to do in pure javascript, but I can not produce the effect correctly. If anyone can see and fix it or guide me to read some tutorial I would appreciate it very much!

Images used:

  • grass : ( link )
  • sand : ( link )

Code:

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

var grass = new Image();
var sand = new Image();
grass.src = "http://i42.tinypic.com/2ljm6w9.png";
sand.src = "http://www.tibiawiki.com.br/images/7/7a/Sand_Tile.gif";

var tsize = 32; //tile size
var mapArray = [[0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,0,0],
                [1,1,1,1,1,1,1,1,1,1,1,1,1]];

//Cam configs

var camX = 0;
var camY = 0;
var camWidth = 320;
var camHeight = 160;

//others
var offsetX = 0;
var offsetY = 0;
function draw() {
    requestAnimationFrame(draw);
    var startCol = Math.floor(camX / tsize);
    var endCol = startCol + (camWidth / tsize);
    var startRow = Math.floor(camY / tsize);
    var endRow = startRow + (camHeight /tsize);
    offsetX = -camX + startCol * tsize;
    offsetY = -camY + startRow * tsize;
    console.log(startCol + "," + endCol + "," + startRow + "," + endRow);
    
    for(var r = startRow; r < endRow; r++){
        for(var c = startCol; c < endCol; c++){ 
            var x = (c - startCol) * tsize + offsetX;
            var y = (r - startCol) * tsize + offsetY;
            if(mapArray[r][c] == 0) {
                ctx.drawImage(grass, x, y);
            }
            if(mapArray[r][c] == 1) {
                ctx.drawImage(sand, x, y);
            }
        }
    }
}

document.addEventListener("keydown", function(e){
    if(e.keyCode == 39) {
        camX += 1;
    }
    
    if(e.keyCode == 37) {
        camX -= 1;
    }
    
});

draw();
<html>
    <head>
        <title>Scrolling Tiled Map</title>
    </head>
    
    <body>
        <canvas id="canvas1" width="320" height="160" style = "border: 1px solid black;"></canvas>
        <script src="javascript.js"></script>
    </body>
</html>

Sorry for the lack of organization with OO, I'm new to programming and I'm also trying to do something simple and functional.

Note: I did not do the proper movement treatment with the arrows.

    
asked by anonymous 24.11.2015 / 03:52

1 answer

6

I gave a neat in your code and it looked like this:

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

var grass = new Image();
var sand = new Image();
grass.src = "https://i.stack.imgur.com/PTwXZ.png";
sand.src = "https://i.stack.imgur.com/TtWhX.gif";
var images = [grass, sand];

var tsize = 32; // Tile size.
var mapArray = [[0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,1,0,0,0],
                [0,0,0,0,0,0,0,0,0,0,0,0,0],
                [1,1,1,1,1,1,1,1,1,1,1,1,1]];

var fieldWidth = mapArray[0].length;
var fieldHeight = mapArray.length;

// Cam configs.

var camX = 0;
var camY = 0;
var camWidth = 320;
var camHeight = 160;

// Others.
var offsetX = 0;
var offsetY = 0;
function draw() {
    requestAnimationFrame(draw);
    var startCol = Math.floor(camX / tsize);
    var endCol = startCol + (camWidth /tsize);
    var startRow = Math.floor(camY / tsize);
    var endRow = startRow + (camHeight / tsize);
    var offsetX = -camX + startCol * tsize;
    var offsetY = -camY + startRow * tsize;
    console.log(startCol + "," + endCol + "," + startRow + "," + endRow);
    
    ctx.fillStyle = "#0000FF";
    ctx.fillRect(0, 0, camWidth, camHeight);
    for (var r = Math.max(0, startRow); r <= endRow && r < fieldHeight; r++) {
        for (var c = Math.max(0, startCol); c <= endCol && c < fieldWidth; c++) {
            var x = (c - startCol) * tsize + offsetX;
            var y = (r - startRow) * tsize + offsetY;
            ctx.drawImage(images[mapArray[r][c]], x, y);
        }
    }
}

document.addEventListener("keydown", function(e) {

    if (e.keyCode == 40) {
        camY += 1;
        e.preventDefault();
    }

    if (e.keyCode == 39) {
        camX += 1;
        e.preventDefault();
    }

    if (e.keyCode == 38) {
        camY -= 1;
        e.preventDefault();
    }

    if (e.keyCode == 37) {
        camX -= 1;
        e.preventDefault();
    }
});

draw();
<html>
    <head>
        <title>Scrolling Tiled Map</title>
    </head>
    
    <body>
        <canvas id="canvas1" width="320" height="160" style = "border: 1px solid black;"></canvas>
        <script src="javascript.js"></script>
    </body>
</html>

The changes I've made are:

  • I have implemented the scroll to the above and below keys. According to Bacco's suggestion, I've added a e.preventDefault() to each key so page scrolling will not occur and will mess up the game experience.

  • I put the images in an array, so it's easier to access them with indexes that are in mapArray . This eliminates the need to use if(mapArray[r][c] == 0) and also makes it very easy to add new shapes.

  • I placed a standard blue background. This background will be visible if your viewport ( camX , camY , camX + camWidth , camY + camHeight ) views areas that might be outside the array, in addition to their borders. Without this, the result is that old pixels were dropped backwards, resulting in dirt and artifacts in the drawing for areas outside the matrix.

  • Here you used startCol instead of startRow :

    var y = (r - startCol) * tsize + offsetY;
    
  • I entered the variables fieldWidth and fieldHeight so I could have the size of the array easily.

  • I added the var keyword in the variables offsetX and offsetY .

  • I changed the initialization in the for loops to the following:

    var r = Math.max(0, startRow);
    var c = Math.max(0, startCol);
    

    The reason for this is to never access mapArray[r][c] when r or c are negative (ie, exceeding the left and / or upper bounds of the array).

  • I changed the stopping condition of the for loops to the following:

    r <= endRow && r < fieldHeight;
    c <= endCol && c < fieldWidth;
    

    The reason for expressions after && is for it not to access mapArray[r][c] when r or c exceeds the right and / or bottom limits of the array.

    The reason I use <= instead of < is that the viewport might not align exactly to the grid of tiles. Thus, although the height of the viewport is equal to 5 tiles, there may be 6 visible tile lines, 4 full lines, half the first line visible at the top, and half the last visible line at the bottom. The same principle also applies to columns.

  • And if you want to prevent the movement from going beyond the grid of tiles, the easiest way is to add this:

        camX = Math.max(0, Math.min(camX, fieldWidth * tsize - camWidth));
        camY = Math.max(0, Math.min(camY, fieldHeight * tsize - camHeight));
    

    The place where this will be added depends on how camX and camY will be handled in the game, but the two most likely places are:

    • The end of the addEventListener function, especially if they are not changed anywhere else than this function.

    • The beginning of the function draw , if you want to correct these values just before the drawing and let them be changed the will outside the function.

    And in this case, when you add these two lines, you can remove the drawing from the blue background, which corresponds to these two other lines:

        ctx.fillStyle = "#0000FF";
        ctx.fillRect(0, 0, camWidth, camHeight);
    
        
    24.11.2015 / 07:29