(Redirected from Ray Casting)

Example project. The walls are ray casted.

A raycaster is a project that renders a three-dimensional scene by "casting rays" from a camera into a scene of objects. The ray's length and closest object hit is information used to then draw on the screen. While the term "ray casting" is broad and unspecifying of direction, in Scratch it is generally used to mean casting rays horizontally only. This article will focus on the horizontal kind to draw walls.

Ray casting should not be mistaken with ray tracing, which renders with more physical accuracy, catering to reflection and refraction of light rays.

It should also not be mistaken with ray marching, where objects are represented as distance functions.

Concept

A visual representation of the ray casting process.

Ray casting works by casting "rays" to measure the distance (or "depth") to the nearest wall, hence the term "ray caster". It can be considered that there are two categories of raycasters on Scratch: sprite-based and list-based.

  • The sprite-based method uses sprites and costumes to represent the shape of the scene and player. A separate sprite moves around to simulate a ray. Intersections are found with the <touching ( v)?> block. The sprites may be invisible with the ghost effect or be quickly shown then hidden so that the player doesn't see the detection take place.
  • The list-based method stores the scene in lists. To find intersections, mathematical formulae can be used. Most often the scene is a 2D array of tiles and in this case, the DDA algorithm is used.

With all methods of casting rays, the hit information is then used to draw a picture on the screen. Each ray casted can be drawn as a vertical column with its length dependent on the reciprocal of hit distance. The color of the column can also be set to give the appearance of atmospheric haze to distant walls.

Sprite-Based Raycaster

At a minimum, a sprite-based raycaster needs a sprite for the walls and a sprite to simulate the rays and sense collisions with the scene. The player can be added as the third sprite.

Walls

An example map.

Make a sprite with a costume showing a map of the walls, preferably 480x360. Everywhere else should be transparent.

Then, add the following script to it:

when gf clicked
go to x: (0) y: (0)
show
set [ghost v] effect to (100)

Player

Next, make a player sprite to move around the map. Give it a costume that is a circle.

Add the following script to it so that it can move around:

when gf clicked
go to x: (0) y: (0) // start position
point in direction (0) // start direction
show
set [ghost v] effect to (100)
forever
  if <key (right arrow v) pressed?> then // turn right
    turn cw (4) degrees
  end
  if <key (left arrow v) pressed?> then // turn left
    turn ccw (4) degrees
  end
  if <key (up arrow v) pressed?> then // move forwards
    move (2) steps
    if <touching (scene v)?> then // wall sensing; prevent the player from moving through a wall
      move (-2) steps
    end
  else
    if <key (down arrow v) pressed?> then // move backwards
      move (-2) steps
      if <touching (scene v)?> then // wall sensing; prevent the player from moving through a wall
        move (2) steps
      end
    end
  end
end

The project in its current state can be tested, although you won't see anything due to the ghost effect. Feel free to temporarily set the ghost effect to 0 to see that the player can move around and not go through walls.

Renderer

The code to perform the ray casts and draw the walls is contained in this sprite. The costume for this needs to be a small dot, preferably 1 pixel in bitmap mode. The small size makes the touching block faster.

when gf clicked
set [ghost v] effect to (100)
show
set [resolution v] to [4] // the width of a pen line measured in pixels
set [field of view v] to [90] // how much of the surroundings are visible, measured in degrees
set [step size v] to [2] // how many pixels to step forwards when ray casting
forever
erase all
draw ray casted walls
end

define draw ray casted walls // enable "run without screen refresh"
set pen color to [#3a00ea]
set pen size to (resolution)
set [columns v] to ((480) / (resolution)) // the number of columns to draw
set [i v] to [0] // counter to keep track of the current column or ray
repeat ([ceiling v] of (columns)) // cast a ray for each column on the screen
  go to (player v) // the ray starts at the player
  point in direction (([direction v] of (player v)) + ((((i) / (columns)) - (0.5)) * (field of view))) // set the angle of the ray relative to the player's direction
  set [depth v] to [0] // how far the ray can travel is stored in this variable
  repeat until <<touching (map v)?> or <(depth) > [500]>> // move the ray forwards until it collides with a wall
    move (step size) steps
    change [depth v] by (step size)
  end
  
  set pen (saturation v) to ((100) - ((depth) * (0.15))) // distant walls get a faded color
  go to x: ((-240) + ([floor v] of (((i) + (0.5)) * (resolution)))) y: ((-4000) / (depth)) // go to the bottom of the column
  pen down
  set y to ((4000) / (depth)) // draw a pen line to the top of the column
  pen up
  
  change [i v] by (1) // increment counter to go to the next column
end

The project is now finished!

Speed Optimization

The main speed bottleneck with a sprite-based raycaster is that the distance sensing sprite has to do lots of sensing. This can be reduced by:

  • Making larger steps (increase the "step size" variable)
  • Drawing fewer columns (increase the "resolution" variable)


Examples


List-Based Raycaster

This article or section may not have content matching Scratch Wiki editing standards. Please improve it according to Scratch Wiki:Guidelines and Scratch Wiki:Editing Conventions. (March 2026)
Reason: Overall needs a clean up or rewrite (see editor comment).
A screenshot from a game that uses the list-based raycasting tutorial. Note that the frame per second counter is not low for a 3D Scratch project.

A list-based raycaster stores the scene in at least one list. This method does not require touching blocks or multiple sprites. This section will cover the most common kind, which is a 2D array of tiles with rays using the DDA algorithm.

If you want an example map of tiles, one is downloadable here.

Note Note: Only try this method after you have completely understood the Sprite-Based raycaster or are already familiar with arrays and raycasting.

Variables Required

The following variables are required for the tutorial:

(Actual Resolution)
(Brightness :: variables)
(Camera X)
(Direction X)
(Direction X Old)
(Direction Y)
(Distance X Delta)
(Distance Y Delta)
(Draw End)
(Draw Start)
(Height)
(Line Height)
(Map X)
(Map XY)
(Map Y)
(Move Speed)
(Perpendicular Wall Distance)
(Plane X)
(Plane X Old)
(Plane Y)
(Ray X Direction)
(Ray X Position)
(Ray Y Direction)
(Ray Y Position)
(Resolution)
(Rotation Speed)
(Side X Distance)
(Side Y Distance)
(side)
(Step X)
(Step Y)
(Touching Wall)
(Wall Found)
(x)
(X Direction)
(X Position::variables)
(Y Direction)
(Y Position::variables)

Lists Required

The following lists are required for the tutorial:

(World Map::list)

Setting up the Variables

The code used to setup the variables is:

define Set up Variables
set [X Position v] to [11]
set [Y Position v] to [7]
set [Direction X v] to [-1]
set [Direction Y v] to [0]
set [Plane X v] to [0]
set [Plane Y v] to [0.66]
set [Actual Resolution v] to [1] // If you aren't using resolution, you don't need this variable.
set [Height v] to [300]
set [Resolution v] to [12] // Resolution is not needed, but it is recommended.

Setting up the Lists

The (World Map::list)list should contain n items of n numbers of either 0 or 1, where n is the distance of each side of the grid that the list represents. Squares that should be filled in should be represented by a 1, and squares that should be empty should be represented as a 0. Here is a example 10x10 world map.

when flag clicked
delete all of [world map v]
add [1111111111] to [world map v]
add [1000000001] to [world map v]
add [1000110001] to [world map v]
add [1000000001] to [world map v]
add [1001001001] to [world map v]
add [1001001001] to [world map v]
add [1000000001] to [world map v]
add [1000110001] to [world map v]
add [1000000001] to [world map v]
add [1111111111] to [world map v]

Pen Shade Replacement

This script is for replacing the set pen shade to () block:

define set pen shade to (shade)
set pen (brightness v) to ((100)-(shade))

The Main Loop

This is the green flag script that starts all the other scripts.

when green flag clicked
Set Up Variables::custom
forever
pen up // DadOfMrLog did some tests and found that setting pen size to 1 and using the pen up block reduces lag
set pen size to (1)
hide
set [Actual Resolution v] to (((Resolution) - (16)) * (-1)) // If you want to have resolution, you need this script.
Raycast::custom // This is the next script in the tutorial.
end

The Raycasting Script

Next is the main raycasting script. This block controls most of the custom blocks. Be sure to make it a run without screen refresh block.

define Raycast
erase all
set [x v] to [-240] // "x" is the increment variable here.
Read Keys::custom // This is the next script in the tutorial.
repeat until <(x) > [240]>
    pen up
    set pen size to (1)
    set pen shade to (0)::custom
    set [Camera X v] to ((2) * ((x) / ((Actual Resolution) - (1)))
    set [Ray X Position v] to (X Position::variables)
    set [Ray Y Position v] to (Y Position::variables)
    set [Ray X Direction v] to ((Direction X) + ((Plane X) * (Camera X)))
    set [Ray Y Direction v] to ((Direction Y) + ((Plane Y) * (Camera X)))
    set [Map X v] to ([floor v] of (Ray X Position))
    set [Map Y v] to ([floor v] of (Ray Y Position))
    Calculate Walls :: custom // Explained later on.
    Draw Walls :: custom // Explained later on.
    change [x v] by (Actual Resolution)
end

Controls

One of the custom blocks in the "raycast" script was the custom block, "Read Keys." Here, we'll focus on that script.

define Read Keys
if <<key (up arrow v) pressed?>and<not <(Touching Wall) = [1]>>> then
        change [X Position v] by ((X Direction) * (Move Speed))
        change [Y Position v] by ((Y Direction) * (Move Speed))
end
if <<key (down arrow v) pressed?>and<not <(Touching Wall) = [-1]>>> then
        change [X Position v] by ((X Direction) * (Move Speed))
        change [Y Position v] by ((Y Direction) * (Move Speed))
end
if <key (right arrow v) pressed?> then
    set [Direction X Old v] to (Direction X)
    set [Direction X v] to (((Direction X) * ([cos v] of ((Rotation Speed) * (-1)))) - ((Direction Y) * ([sin v] of ((Rotation Speed) * (-1)))))
    set [Direction Y v] to (((Direction X Old) * ([sin v] of ((Rotation Speed) * (-1)))) + ((Direction Y) * ([cos v] of ((Rotation Speed) * (-1)))))
    set [Plane X Old v] to (Plane X)
    set [Plane X v] to (((Plane X) * ([cos v] of ((Rotation Speed) * (-1)))) - ((Plane Y) * ([sin v] of ((Rotation Speed) * (-1)))))
    set [Plane Y v] to (((Plane X Old) * ([sin v] of ((Rotation Speed) * (-1)))) + ((Plane Y) * ([cos v] of ((Rotation Speed) * (-1)))))
end
if <key (left arrow v) pressed?> then
    set [Direction X v] to (Direction X)
    set [Direction X v] to (((Direction X Old) * ([cos v] of ((Rotation Speed) * (1)))) - ((Direction Y) * ([sin v] of ((Rotation Speed) * (1)))))
    set [Direction Y v] to (((Direction X Old) * ([sin v] of ((Rotation Speed) * (1)))) + ((Direction Y) * ([cos v] of ((Rotation Speed) * (1)))))
    set [Plane X Old v] to (Plane X)
    set [Plane X v] to (((Plane X) * ([cos v] of ((Rotation Speed) * (1)))) - ((Plane Y) * ([sin v] of ((Rotation Speed) * (1)))))
    set [Plane Y v] to (((Plane X Old) * ([sin v] of ((Rotation Speed) * (1)))) + ((Plane Y) * ([cos v] of ((Rotation Speed) * (1)))))
end

Calculating Walls

This section explains the block that calculates the walls. Unfortunately, wall touch detection is not included.

define Calculate Walls
set [Touching Wall v] to [0]
set [Distance X Delta v] to ([sqrt v] of ((1) + (((Ray Y Direction) * (Ray Y Direction)) / ((Ray X Direction) * (Ray X Direction)))))
set [Distance Y Delta v] to ([sqrt v] of ((1) + (((Ray X Direction) * (Ray X Direction)) / ((Ray Y Direction) * (Ray Y Direction)))))
set [Wall Found v] to [0] // Once the ray hits a wall, this variable is set to one, which is the same as the boolean value "true" in this case.
if <(Ray X Direction) < [0]> then
    set [Step X v] to [-1]
    set [Side X Distance v] to (((Ray X Position) - (Map X)) * (Distance X Delta))
else
    set [Step X v] to [1]
    set [Side X Distance v] to ((((Map X) + (1)) - (Ray X Position)) * (Distance X Delta))
end
if <(Ray Y Direction) < [0]> then
    set [Step Y v] to [-1]
    set [Side Y Distance v] to (((Ray Y Position) - (Map Y)) * (Distance Y Delta))
else
    set [Step Y v] to [1]
    set [Side Y Distance v] to ((((Map Y) + (1)) - (Ray Y Position)) * (Distance Y Delta))
end
repeat until <(Wall Found) = [1]>
    if <(Side X Distance) < (Side Y Distance)> then
        change [Side X Distance v] by (Distance X Delta)
        change [Map X v] by (Step X)
        set [side v] to [0] // The variable "side" is referring to which wall is being faced, a Y wall or an X wall. In this case, it is an X wall.
    else
        change [Side Y Distance v] by (Distance Y Delta)
        change [Map Y v] by (Step Y)
        set [side v] to [1] // In this case, the ray has hit a Y wall.
    end
    set [Map XY v] to (letter (Map Y) of (item (Map X) of [World Map v])) // Map XY is the item of the grid that the ray is in. Example: if X is eleven and Y is seven, then Map XY would be (letter 7 of (item 11 of World Map).
    if <(Map XY) > [0]> then // If Map XY is more than zero, it has hit a wall.
        set [Wall Found v] to [1]
    end
end
if <(side) = [0]> then
    set [Perpendicular Wall Distance v] to ([abs v] of ((((Map X) - (Ray X Position)) + (((1) - (Step X)) / (2))) / (Ray X Direction)))
else
    set [Perpendicular Wall Distance v] to ([abs v] of ((((Map Y) - (Ray Y Position)) + (((1) - (Step Y)) / (2))) / (Ray Y Direction)))
end
set [Line Height v] to ([abs v] of ([floor v] of ((360)/(Perpendicular Wall Distance))
set [Draw Start v] to ((Line Height) / (-2)
set [Draw End v] to ((Line Height) / (2))

Drawing the Walls

The last part in this tutorial is the pen script that draws the walls. It is a very simple script.

define Draw Walls
set pen color to [#179fd7]
if <(side) = [1]> then
    set [Brightness v] to (115) // This script makes the Y walls darker than the X walls for a nice effect.
else
    set [Brightness v] to (150)
end
go to x: (x) y: (Draw Start)
set pen shade to ((Brightness :: variables) * (-0.2))::custom
set pen size to (Actual Resolution)
pen down
go to x: (x) y: ((2) * (Draw End))
pen up
set pen size to (1)
set pen shade to (0)::custom

Your list-based raycaster is now ready!

See Also

External Links

Cookies help us deliver our services. By using our services, you agree to our use of cookies.