Blog posts about my javascript gamedev journey with ct.js

Pan and zoom a map with ct.js

1) Generate the map

We’ll use copies for that example, not tiles, but it could be the subject of another post.

Our map will be a 2D array.
First, we declare it and its size.

var map = [];
var mapWidth = 100;
var mapHeight = 100;

Then, we can create an empty 2D array like this:

for (var y = 0; y < mapHeight; y++) {
    map[y] = new Array(mapWidth);
}

Now, we fill it with random numbers:

for (var x = 0; x < mapWidth; x++) {
    for (let y = 0; y < mapHeight; y++) {
        // Border the map with 0 (they will be block)
        if (x == 0 || y == 0 || x == mapWidth - 1 || y == mapHeight - 1) {
            map[y][x] = 0
        }
        // Basic randomization for the rest of the map
        else {
            // Random integer between 0 and 5 (because we'll display 6 types of copy)
            map[y][x] = Math.floor(Math.random() * 6)
        }
    }
}

After few lines of code, we can already display the map.
Well, of course, before, you need to create 6 textures and 6 types in ct.js.
Let’s put their name in an array:

var copyTypes = [
    "Block1", // At index 0 put the name of the type you create for the block 1
    "Block2", // At index 1 put the name of the type you create for the block 2
    "Block3", // At index 2 put the name of the type you create for the block 3
    "Floor1", // At index 3 put the name of the type you create for the floor 1
    "Floor2", // At index 4..., I guess you get it ; )
    "Floor3"
];

Thanks to that array of types’ name, we are ready to easily display the map (no need of if / else if):

var copySize = 16; // 16 or else, depends on your textures' size

// While we browse our 2D map array, we'll keep track of the position (in pixel) of the copy
var xCopy = 0;
var yCopy = 0;

for (var y = 0; y < mapHeight; y++) {
    // When we read a new row of our 2D array, we reset xCopy to 0
    xCopy = 0; 
    for (var x = 0; x < mapWidth; x++) {
        // We add a the right copy thanks to our copyTypes array
        ct.types.copy(copyTypes[map[y][x]], xCopy, yCopy);
        // At each column of our 2D array, we add the size of a copy to xCopy
        xCopy += copySize;
    }
    // At each row of our 2D array, we add the size of a copy to yCopy
    yCopy += copySize;
}

At that point, you can “Launch” the project, you’ll get the map displayed! : )

2) Deal with zoom

First, you need to add an entry in the ct.js “Action and input methods” tab.
I called it “Zoom” and chose “mouse.Wheel”.
Then, in the “On Create” of the room (it could also be in a script), declare a function and a zoom variable:

this.zoom = 1;
this.cameraZoom = (val) => {
    this.zoom += val;
    ct.camera.scale = 1 / this.zoom;
}

Finally, in the “On Step” you can “listen to” the mouse wheel event to change the zoom value and the camera.scale according to that new zoom value:

// Mouse wheel up
if (ct.actions.Zoom.value === 1) {
    // 10 is arbitrary, but you don't want to scroll forever, right?
    if (this.zoom < 10) {
        this.cameraZoom(1);
    }
}
// Mouse wheel down
else if (ct.actions.Zoom.value === -1) {
    // Zoom at 0 would display nothing
    if (this.zoom > 1) {
        this.cameraZoom(-1);
    }
}

3) Deal with pan

Here again, firstly, you need to add 4 entries in the ct.js “Action and input methods” tab.
One for each cardinal direction. Use WASD or arrow keys, or both, it’s up to you.
And put something like this in the “On Step” of your room:

if (ct.actions.PanMapN.down) {
    ct.camera.targetY -= 10;
}

if (ct.actions.PanMapE.down) {
    ct.camera.targetX += 10;
}

if (ct.actions.PanMapS.down) {
    ct.camera.targetY += 10;
}
if (ct.actions.PanMapW.down) {
    ct.camera.targetX -= 10;
}

4) Limit the pan

That function computes the min/max (x and y) values for pan:

this.panCamera = function () {
    ct.camera.minX = 0;
    ct.camera.minY = 0;
    ct.camera.maxX = mapWidth * copySize;
    ct.camera.maxY = mapHeight * copySize;

    if (ct.camera.targetX < ct.camera.minX) {
        ct.camera.targetX = ct.camera.minX;
    }
    if (ct.camera.targetY < ct.camera.minY) {
        ct.camera.targetY = ct.camera.minY;
    }
    if (ct.camera.targetX > ct.camera.maxX) {
        ct.camera.targetX = ct.camera.maxX;
    }
    if (ct.camera.targetY > ct.camera.maxY) {
        ct.camera.targetY = ct.camera.maxY;
    }
}

Last task: simply call this function in room “Draw”:

this.panCamera();

Conclusion

That’s all!
I hope that small overview will be useful to some of you and maybe give you some ideas.
I tried to keep the code simple.
I usually put stuff in objects and scripts (reusable in any room), use arrow functions, const and let, etc. but here, better go straight to the point imo.
Anyway, it’s time to let you code. ; )

Have a good gamedev!

Published on 08-04-2021 by Thomas