Search code examples
javascriptcanvas3dcamera2.5d

pseudo 3D camera turning


I am trying to learn to make 3D games in JavaScript using HTML 2D canvas. I was following this post about it and I made a simple scene that you can move around in.

What I need help with is figuring out how to make the effect of the player turning their head, to look side to side and behind them.

Here is what I have:

Codepen link

Code (also on codepen)

html:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3d test</title>
</head>
<body>
    <canvas id="canv"></canvas>
</body>
</html>

javascript

//use arrow keys to move around

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

canvas.width = canvas.height = 800;

var crateImg = new Image();

class Entity {
    constructor(x, y, z, w, h) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        this.h = h;
        this.rx = 0;
        this.ry = 0;
        this.rs = 0;
    }
    render() {
        //c.fillStyle = 'red';
        //c.fillRect(this.rx, this.ry, this.rs*this.w, this.rs*this.h);
        c.drawImage(crateImg, this.rx, this.ry, this.rs*this.w, this.rs*this.h);
    }
    update() {
        //project to 3d
        this.rs = 400/(400+this.z);
        this.rx = ((this.x*this.rs) + 400);
        this.ry = (this.y*this.rs) + 400;


        //move
        this.x += camera.xSpeed;
        this.y += camera.ySpeed;
        this.z += camera.zSpeed;
    }
}

var camera = {
    xSpeed: 0,
    ySpeed: 0,
    zSpeed: 0,
}

var entities = [];

function random(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
}

window.onload = function() {
    start();
    update();
}

function start() {
    crateImg.src = "https://i.imgur.com/O9ForWS_d.webp?maxwidth=760&amp;fidelity=grand";
    for(let i = 0; i < 100; i++) {
        entities.push(new Entity(random(-800, 800), 0, i*10, 50, 50));
    }
}

function render() {
    //fill background
    c.fillStyle = 'skyblue';
    c.fillRect(0, 0, 800, 800);
    //draw flooor
    c.fillStyle = 'green';
    c.fillRect(0, 400, 800, 400);
    //draw entities
    for(let i = 0; i < entities.length; i++) {
        if(entities[i].z > -400) {
            entities[i].render();
        }
    }
}

function update() {
    //updatre entities
    for(let i = 0; i < entities.length; i++) {
        entities[i].update();
    }
    entities.sort(function(i, i2) {
        return i2.z - i.z;
    })
    //redraw current frame
    render();
    requestAnimationFrame(update);
}


function keyDown(e) {
    switch(e.keyCode) {
        case 39:
            camera.xSpeed = -5;
            break;
        case 37:
            camera.xSpeed = 5;
            break;
        case 38:
            camera.zSpeed = -5;
            break;
        case 40:
            camera.zSpeed = 5;
            break;
    }
}

function keyUp(e) {
    switch(e.keyCode) {
        case 39:
        case 37:
            camera.xSpeed = 0;
            break;
        case 38:
        case 40:
            camera.zSpeed = 0;
            break;
    }
}

document.onkeydown = keyDown;
document.onkeyup = keyUp;

Solution

  • First of all, you should not update the coordinates of the crates from the movement of the camera. Instead, let the camera have its own position in 3D space, update that position when you want the player to move, and then subtract the camera position from the crates' positions when calculating the 2D space coordinates. This will make things much easier when you later want to add, for example, the ability for the camera to rotate or the crates themselves to move.

    Now to add a rotation capability to the camera, you will need to define a rotation matrix for it in addition to a position. Then, when rendering the crates, you transform the crate coordinates (after subtracting the camera's position) using the inverse of the camera's rotation matrix. This will give you the view space coordinates of the crates, which should be projected onto the 2D space for rendering.

    There are many ways to create a rotation matrix depending on what parameters you have. A common one is the Rodrigues rotation formula or "axis-angle" formula, which is used when you have an axis of rotation and an angle to rotate about that axis. Another one is from Euler angles. And if you aren't familiar with matrices and linear algebra, I would recommend you to learn it (there are a lot of free resources available online) as it is used extensively in 3D game development.