Skip to content

Background

Vinicius Reif Biavatti edited this page Apr 16, 2021 · 10 revisions

Background

The background rendering is easy to implement. There is no mistery for it. In this tutorial step we will draw a background image in the projection so, the frist thing to do is get our image.

Our image needs to have the size in relation of the projection size. The background is drawed from the top to the middle height in the screen so, the image height will have the half height of our projection. The width of the image is relationed of the player angle. The value will be 360, because the image will repeat when the player get a full rotate. In this case:

Background image width: 360px Background image height: 120px

Note: For the height, we will not use the screen height because we are using a scale for rendering. The height of the image will be the value of the data.projection.halfHeight attribute.

I created the image below for the tutorial, but you can create your awn background ok?!

File: background.png

Background RayCasting rendering

After get our image, we will need to import this image in our HTML page. For it, we will do the same thing we did to import the textures. Put the image file in our project root directory and reference it with the <img /> tag.

<!-- ... -->
<body>
    <!-- ... -->
    <img id="background" src="background.png" style="display: none">
</body>
<!-- ... -->

So now, we have to define this image in our attributes. We will create the backgrounds key in our data object and after it, we will define our background object inside, with the width, height, id and data. The id will be the identifier of the element in the HTML and the data will receive the pixel array of our image.

// Data
let data = {
    // ...
    backgrounds: [
        {
            width: 360,
            height: 60,
            id: "background",
            data: null
        }
    ]
}

After it, we will create the function that will be used to import the image data for our background(s). The function will make the same thing of the loadTextures() function. We will just need to change the properties. Lets create the loadBackgrounds() function.

/**
 * Load backgrounds
 */
function loadBackgrounds() {
    for(let i = 0; i < data.backgrounds.length; i++) {
        if(data.backgrounds[i].id) {
            data.backgrounds[i].data = getTextureData(data.backgrounds[i]);
        }
    }
}

Now, we will call this function after call the loadTextures() function.

// Start
window.onload = function() {
    loadTextures();
    loadBackgrounds();
    main();
}

Well. The background image will be imported. The next thing to do is to define the draw function for backgrounds. This new function will replace the first draw function of the rayCasting() function. This draw function was used to draw the sky, but now, we will draw the sky with the background texture. Lets create the drawBackground() function! This function will need four parameters:

  • x: This will be the x-axis position to draw
  • y1: The y-axis to start to draw the pixels
  • y2: The y-axis to stop to draw the pixels
  • background: The background object of our data.backgrounds
/**
 * Draw the background
 * @param {number} x 
 * @param {number} y1 
 * @param {number} y2 
 * @param {Object} background 
 */
function drawBackground(x, y1, y2, background) {
}

Now, we have to get the angle of the player to discover what is the x coordinate of the image we need to start to scan. This angle will be added with the x-axis parameters. We will be call this variable as offset. The second thing to do is create the loop from the y1 to the y2, to make the line with the image pixels. Inside this loop we need to get the pixel from the image data.

/**
 * Draw the background
 * @param {number} x 
 * @param {number} y1 
 * @param {number} y2 
 * @param {Object} background 
 */
function drawBackground(x, y1, y2, background) {
    let offset = (data.player.angle + x);
    for(let y = y1; y < y2; y++) {
    }
}

Inside the loop, we will define two variables that represents the coordinates of the image. When the player is with angle 0, the image will be drawed from x: 0 to x: 60. When the player is with angle 320 for example, the image will be drawed from x: 320 to x: 20. For this case there is a repetition so, we will have to use the module % operator to make the offset.

/**
 * Draw the background
 * @param {number} x 
 * @param {number} y1 
 * @param {number} y2 
 * @param {Object} background 
 */
function drawBackground(x, y1, y2, background) {
    let offset = (data.player.angle + x);
    for(let y = y1; y < y2; y++) {
        let textureX = Math.floor(offset % background.width);
        let textureY = Math.floor(y % background.height);
    }
}

After it, we need just to get the color from the image data and draw the pixel using the drawPixel() function. The image data is an array, so we have to use the formula we learned to get the correct pixel.

Note: We have to use the Math.floor() function to get the integer result value. If we don't use this, our application will throw an exception when we try to get the pixel of the image.

/**
 * Draw the background
 * @param {number} x 
 * @param {number} y1 
 * @param {number} y2 
 * @param {Object} background 
 */
function drawBackground(x, y1, y2, background) {
    let offset = (data.player.angle + x);
    for(let y = y1; y < y2; y++) {
        let textureX = Math.floor(offset % background.width);
        let textureY = Math.floor(y % background.height);
        let color = background.data[textureX + textureY * background.width];
        drawPixel(x, y, color); 
    }
}

Well done! With our function created we just need to call it instead of call the drawLine() function inside our rayCasting() function.

/**
 * Raycasting logic
 */
function rayCasting() {
    // ...

    // Draw
    drawBackground(rayCount, 0, data.projection.halfHeight - wallHeight, data.backgrounds[0]);
    drawTexture(rayCount, wallHeight, texturePositionX, texture);
    drawLine(rayCount, data.projection.halfHeight + wallHeight, data.projection.height, new Color(95, 87, 79, 255));

    // ...
}

Note: I used the constant data.backgrounds[0] reference, but we can define some attribute in our data object to reference the background that will be used for the current level for example.

Result

The result of this tutorial step is:

Result of the RayCasting projection with background

Code

This is the full code of this tutorial step:

/**
 * Load backgrounds
 */
function loadBackgrounds() {
    for(let i = 0; i < data.backgrounds.length; i++) {
        if(data.backgrounds[i].id) {
            data.backgrounds[i].data = getTextureData(data.backgrounds[i]);
        }
    }
}

/**
 * Draw the background
 * @param {number} x 
 * @param {number} y1 
 * @param {number} y2 
 * @param {Object} background 
 */
function drawBackground(x, y1, y2, background) {
    let offset = (data.player.angle + x);
    for(let y = y1; y < y2; y++) {
        let textureX = Math.floor(offset % background.width);
        let textureY = Math.floor(y % background.height);
        let color = background.data[textureX + textureY * background.width];
        drawPixel(x, y, color); 
    }
}

Our RayCasting is getting better for each finished tutorial. Continue like this!