Добро пожаловать в черновик (песочницу)!
Данная страница создана для экспериментов с редактированием, так что не стесняйся попробовать свой опыт форматирования текста в этом месте. Чтобы отредактировать страницу, кликни на значок карандаша в верхнем-правом углу, выбери Править в выпадающем меню, сделай свои изменения в диалоговом окне и нажми на кнопку Записать страницу когда ты закончишь. Текст, добавленный здесь будет удалён за некоторое время.
Кликни сюда, чтобы создать свой черновик. Для справки по редактированию кликни сюда.

Пожалуйста, не размещайте контент, который неприемлем для Скретча.

:англ: :нем:

Рейкастинг (англ. ray casting или бросание лучей) — тип проектов, которые отрисовывают 3D-мир на основе 2D-поверхности. Рейкастеры (проекты рейкастинга, англ. Raycaster) в Скретче обычно воспроизводятся без обновления экрана и в низком разрешении для предотвращения задержки.

Рейкастинг не следует путать с рейтрейсингом (англ. ray tracingтрассировка лучей), что отрисовывает лучи с большей физической точностью, обслуживанием отражения и преломления световых лучей и трассирует лучи в двух измерениях, а не в одном, как в рейкастинге.

## Принцип

Рейкастинг работает путём отбрасывания «лучей» для того, чтобы измерить расстояние к ближайшей стене, отсюда и термин «бросание лучей». Программа отправляет лучи от игрока, которые двигаются вперёд до прикосновения с объектом, и измеряет расстояние движения и цвета, основываясь на расстоянии. Лучи отправляются в различных направлениях. После отправки всех лучей, будет видна полноценная картина.

## Сравнение методов

Данная статья объясняет три метода создания рейкастеров — метод со спрайтами, метод со списками и метод с функциями знаковых расстояний. У каждого есть свои плюсы и минусы, указанные в таблице ниже:

Метод со спрайтами Метод со списками Метод с функциями знаковых расстояний
Степень сложности Лёгкая Сложная Сложная
Частота кадров Низкая Высокая Низкая
Необходимые знания Базы программирования на Скретче Программирование на Скретче; тригонометрия; списки/массивы Программирование на Скретче; тригонометрия
Способ хранения карты Как спрайт; каждая карта/мир является костюмом Как список (массив чисел); каждая карта есть другой список Как группа пользовательских блоков
Необходимые спрайты 1 или более, в зависимости от настройки Хотя бы 1 (отрисовщик) Хотя бы 1 (отрисовщик)
Другие плюсы Может быть использован для создания миров с кривыми стенами Может с лёгкостью генерировать случайные миры Может с лёгкостью генерировать фигуры вроде сфер, торов и конусов

For beginners, it is recommended to start by making the sprite-based method, as it is easy and not complicated. For more advanced programmers and those who have made a sprite-based raycaster, it is recommended to make an array based raycaster, which is faster but more complicated, or an SDF based raycaster, which is slower and more complicated, but more flexible.

## Sprite-Based Raycaster

The necessary components of a raycaster are:

• A "map"
• The map shows the layout of the level.
• A "sensor"
• The sensor will compute distances from the player to each wall.
• A "renderer"
• The renderer draws each wall based on the distance given by the "sensor".

This tutorial will use four sprites to accomplish these three requirements. If someone is stuck at any time, here is a finished example.

### Map

Make a map sprite with walls, preferably 480x360. Floor space should be "clear" colored.

Then, add the following script to it:

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

### Person

Next, a person sprite that walks around the map will be made.

Make a person sprite that has a one pixel by one pixel costume. The costume should be centered on the pixel. This is important.

Before diving into the sprite's scripts, note that all `(speed)` should be replaced with how fast the player needs to move.

Put these scripts in the "person" sprite:

```when gf clicked
go to x: (0) y: (10) // Or wherever you want the sprite in the map.
set [ghost v] effect to (100)
forever
if <key (right arrow v) pressed?> then
turn cw (3) degrees // This can be adjusted to rotate faster, to fight lag.
else
if <key (left arrow v) pressed?> then
turn ccw (3) degrees // This can be adjusted to rotate faster, to fight lag.
end
end
if <key (up arrow v) pressed?> then
move (speed)::custom
else
if <key (down arrow v) pressed?> then
move ((-1) * (speed))::custom
end
end
end
```
```define move (speed)
move (speed) steps
if <touching (Map v)?> then // Wall sensing
move ((-1) * (speed)) steps
end
```

A map has now been created that can be walked around in

### Distance sensing

This is where it starts to build up to 3D raycasting.

In the real world, the more distant an object is, the smaller it appears. A gif showing off "distance sensing" in slow-motion action. The red "blob" is the person sprite.

You will make a sensing script that measures the distance from the player to the walls around them. You need to do this for 96 different angles. All of this will happen when the "sense" broadcast is called.

Make a "distance sensing" sprite that has a one pixel by one pixel costume. The costume should be centered on the pixel.

```when gf clicked
set [ghost v] effect to (100)

when I receive [sense v] // Used in "person"
sense

define sense// Important: Make this custom block run without screen refresh.
. . .
delete all of [distances v]
set [distance v] to 
set [angle offset v] to [-48]
repeat (96)
set [distance v] to 
go to (person v)
point in direction (([direction v] of (person v)) + (angle offset))
repeat until <<touching (map v)?> or <(distance::variables) = >>
move (1) steps
change [distance v] by (1)
end
add (distance::variables) to [distances v] // We hit a wall. Let's keep track of the distance.
change [angle offset v] by (1) // Let's test a new angle...
end
```

### Drawing

Here is the information generated by "distance sensing" it taken and drawn. Remember, the more distant an object is, the smaller it is.

Make a "drawing" sprite with this script:

```when I receive [draw v]
draw

define draw// Important: Make this custom block run without screen refresh.
. . .
go to x: (-237.5) y: (180)
set pen size to (5)
set pen color to [#7AF] // Pick the color that you want.
erase all // Prepare the screen for drawing!
pen up
set [column v] to (1)
repeat (length of [distances v])
set pen (brightness v) to ((50) + ((item (column) of [distances v]) * ((50) / (80)))) // Fade to white
set y to ((-1200) / (item (column) of [distances v])) // Adjust -1200 to make walls larger or smaller.
pen down
set y to ((1200) / (item (column) of [distances v])) // Adjust 1200 to make walls larger or smaller.
pen up
change x by (5)
change [column v] by (1)
end
```

The project is now finished. The project will be slow when it is run in Scratch, unfortunately.

### Jumping (optional)

Шаблон:Wiki Standards Although it is impossible to add true vertical movement to the project, it is possible to create the illusion of jumping. It works by drawing the walls at a lower height when the player is jumping. Walls closer to the player will move downwards more to simulate parallax. To add the option to jump, first add the script below to any sprite or background:

```when gf clicked
forever
if <<key (space v) pressed?> and <(variable) = (1200)>> then
set [variable v] to (900)
set [variable v] to (750)
set [variable v] to (675)
set [variable v] to (650)
set [variable v] to (650)
set [variable v] to (675)
set [variable v] to (750)
set [variable v] to (900)
set [variable v] to (1200)
end
end

```

Now, modify the script that draws the walls to the following:

```define draw
go to x: (-237.5) y: (180)
set pen size to (5)
set pen color to [#7AF] // Pick the color that you want.
erase all
pen up
set [column v] to (1)
repeat (length of [distances v])
set pen (brightness v) to ((50) + ((item (column) of [distances v]) * ((50) / (80))))
set y to (((variable) - (2400)) / (item (column) of [distances v])) //This is what creates the illusion of jumping.
pen down
set y to ((variable) / (item (column) of [distances v])) //This is what creates the illusion of jumping.
pen up
change x by (5)
change [column v] by (1)
end
```

### Speed Optimization

There are several ways to reduce lag:

• Set the "person" sprite's rotation style to "don't rotate".
• In the "distance sensing" sprite, check for walls every 2 steps instead of 1.
• Instead of drawing 96 columns, draw fewer. 60 is a good amount, assuming that in "distance sensing", the sprite is rotated 2 degrees instead of 1 and the pen size is 8.
• Replace the code in the forever loop in the "Player" sprite with this:
```turn cw ((<key (right arrow v) pressed?> - <key (left arrow v) pressed?>) * (5)) degrees
move ((<key (up arrow v) pressed?> - <key (down arrow v) pressed?>) * (speed)) :: custom
```
• Make the sensing both sense and draw
• Replace all `broadcast (sense v) and wait` with `wait (0) seconds` and make the sensing and drawing always sense and draw

The main speed bottleneck with a sprite-based raycaster is that the "distance sensing" sprite has to do lots of sensing.

## List-Based Raycaster

A list-based raycaster relies on a map stored as a list and coordinates, such as that of the player and the ray, stored as variables. This method of raycasting is very virtual, all data is stored as numbers and there are no actual sprite costumes used. The only sprite required is a moving pen to draw the walls. If you want an example map, one is downloadable here. An example is available here. 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.

This tutorial will teach you how to make a simple list-based raycaster. It uses custom blocks to make editing easier. Make sure to check off "run without screen refresh" box. Only try this method after you have completely understood the Sprite-Based Raycaster or are already familiar with arrays and raycasting. Tutorial is not finished.

### 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 
set [Y Position v] to 
set [Direction X v] to [-1]
set [Direction Y v] to 
set [Plane X v] to 
set [Plane Y v] to [0.66]
set [Actual Resolution v] to  // If you aren't using resolution, you don't need this variable.
set [Height v] to 
set [Resolution v] to  // 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  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
add  to [world map v]
```

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) > >
pen up
set pen size to (1)
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) = >>> 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 
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  // 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) < > 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 
set [Side X Distance v] to ((((Map X) + (1)) - (Ray X Position)) * (Distance X Delta))
end
if <(Ray Y Direction) < > 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 
set [Side Y Distance v] to ((((Map Y) + (1)) - (Ray Y Position)) * (Distance Y Delta))
end
repeat until <(Wall Found) = >
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  // 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  // 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) > > then // If Map XY is more than zero, it has hit a wall.
set [Wall Found v] to 
end
end
if <(side) = > 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) = > 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)
```

## SDF-Based Raycaster Статья не завершена! Если ты хочешь помочь — отредактируй её.

An SDF based raycaster uses SDFs, which stands for Signed Distance Functions. These are calculations and instructions used to see if the ray has touched an object. Another thing that makes this special is that it draws individual pixels instead of colums, and can easily draw objects at different heights.

### Variables needed

```(Ray X)
(Ray Y)
(Ray Z)
(Length)
(Origin X)
(Origin Y)
(Origin Z)
(Camera X)
(Camera Y)
(Camera Z)
(Collided?)
(Distance :: variables)
(Render Distance)
(Pen Size)
(Nearest Distance)
(Color)
```

### Main Loop

This starts all the other scripts.

```when gf clicked
erase all
set [Render Distance v] to (5000)
set [Pen Size v] to (15) //Can be any size greater than 0 but this is better for speed
set [Camera X v] to (0)
set [Camera Y v] to (0)
set [Camera Z v] to (-100)
go to x: (-240) y: (-180)
forever
go to x: (-240) y: (-180)
repeat (((360)/(Pen Size))+(1))
set x to (-240)
repeat (((480)/(Pen Size))+(1))
read keys :: custom //We will cover this later
raycast pixel :: custom //Next script in the tutorial
change x by (Pen Size)
end
change y by (Pen Size)
end
erase all
end
```

### Raycasting Script

This script is what casts the ray.

```define raycast pixel
set [Nearest Distance v] to (Render Distance)
set [Collision? v] to (0)
set [Ray X v] to (Center X)
set [Ray Y v] to (Center Y)
set [Ray Z v] to (Center Z)
set [Distance v] to (0)
set pen color to (#000000)
repeat until <<(Collision?) = (1)> or <(Dist) > (Render Distance)>>
check distances :: custom //Will be covered later
move (5) steps in direction (y position) (x position) :: custom // Next in tutorial
end
if <(Collision?) = (1)> then
set pen color to (#FF0000)
set pen (color v) to (Color)
end
pen down
pen up
```

### Moving the Ray

```define move (n) steps in direction (rotation x) (rotation y)
change [Ray X v] by ([sin v] of (rotation y))
change [Ray Y v] by ([cos v] of (rotation y))
change [Ray Z v] by ([sin v] of (rotation x))
```

### Checking If the Ray Has Collided

This is the distance function that will be used to get the distance from any point to the ray position.

```define length (a) (b) (c)
set [Length v] to ([sqrt v] of ((((a) - (Ray X)) * ((a) - (Ray X))) + ((((b) - (Ray Y)) * ((b) - (Ray Y))) + (((c) - (Ray Z)) * ((c) - (Ray Z)))))
```

This checks collision with a sphere.

```define sphere (x) (y) (z) (radius) (color)
length (x) (y) (z) :: custom //Distance from center of sphere to ray
set [Distance v] to ((Length) - (Radius))
collision (color)

define collision (color)
if <not<(Distance :: variables) > (0)>> then
if <(Distance :: variables) < (Nearest Distance)> then
set [Nearest Distance v] to (Distance :: variables)
set [color v] to (color)
end
set [collision v] to (1)
```

This block contains all the SDFs and will check collisions with all objects.

```define check distances
sphere (0) (0) (0) (50) (50) :: custom //Add as much as you want
```

### Moving the Camera

```define read keys
change [Camera X v] by (<key (d v) pressed?> - <key (a v) pressed?>)
change [Camera Y v] by (<key (q v) pressed?> - <key (z v) pressed?>)
change [Camera Z v] by (<key (w v) pressed?> - <key (s v) pressed?>)
``` Для большей информации посмотрите статью «Ray casting» на Википедии.