Since the question is not related to any language, I answer from a Javascript perspective.
I implemented the so called "vertical floor/ceiling" technique as well.
But instead of drawing pixels per pixel with ctx.drawImage()
I use putImageData
.
First I get the data from the tiles I want to render using a temporary canvas:
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = 64;
tempCanvas.height = 64;
var wallsSprite = new Image();
wallsSprite.onload = function () {
tempCtx.drawImage(wallsSprite, 0, 128, 64, 64, 0, 0, 64, 64);
floorData = tempCtx.getImageData(0, 0, 64, 64);
tempCtx.drawImage(wallsSprite, 0, 192, 64, 64, 0, 0, 64, 64);
ceilData = tempCtx.getImageData(0, 0, 64, 64);
}
wallsSprite.src = "./walls_2.png";
I create an empty imageData:
var floorSprite = this.ctx.createImageData(600, 400);
Then I do my "vertical floor/ceiling" raycasting:
//we check if the wall reaches the bottom of the canvas
// this.wallToBorder = (400 - wallHeight) / 2;
if (this.wallToBorder > 0) {
// we calculate how many pixels we have from bottom of wall to border of canvas
var pixelsToBottom = Math.floor(this.wallToBorder);
//we calculate the distance between the first pixel at the bottom of the wall and the player eyes (canvas.height / 2)
var pixelRowHeight = 200 - pixelsToBottom;
// then we loop through every pixels until we reach the border of the canvas
for (let i = pixelRowHeight; i < 200; i += 1) {
// we calculate the straight distance between the player and the pixel
var directDistFloor = (this.screenDist * 200) / (Math.floor(i));
// we calculate it's real world distance with the angle relative to the player
var realDistance = (directDistFloor / Math.cos(this.angleR));
// we calculate it's real world coordinates with the player angle
this.floorPointx = this.player.x + Math.cos(this.angle) * realDistance / (this.screenDist / 100);
this.floorPointy = this.player.y + Math.sin(this.angle) * realDistance / (this.screenDist / 100);
// we map the texture
var textY = Math.floor(this.floorPointx % 64);
var textX = Math.floor(this.floorPointy % 64);
// we modify floorSprite array:
if (floorData && ceilData) {
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600] = floorData.data[textY * 4 * 64 + textX * 4]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 1] = floorData.data[textY * 4 * 64 + textX * 4 + 1]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 2] = floorData.data[textY * 4 * 64 + textX * 4 + 2]
floorSprite.data[(this.index * 4) + (i + 200) * 4 * 600 + 3] = 255;
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600] = ceilData.data[textY * 4 * 64 + textX * 4]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 1] = ceilData.data[textY * 4 * 64 + textX * 4 + 1]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 2] = ceilData.data[textY * 4 * 64 + textX * 4 + 2]
floorSprite.data[(this.index * 4) + (200 - i) * 4 * 600 + 3] = 255;
}
}
}
}
}
finally we draw the floor and ceiling before the walls are rendered:
this.ctx.putImageData(floorSprite, 0, 0);
The result is super fast since:
- we don't need to calculate ceiling texture coordinates since we deduce them from the floor coordinates.
- we draw the ceiling/floor only once per loop, not pixels per pixel.
- only the pixels that are visible are redrawn so it doesn't
wastes performance on filling up the whole screen with floor and ceiling, and after that it draws walls
.
Maybe it could be optimized with mixing horizontal raysting
and putImageData
put the game speed with wall/ceiling rendering or without is almost the same.
Here is the result