Monday 21 May 2012

Wolfenstein 3d (part 1)

It's the anniversary of the game of Wolfenstein3D, and the question is: is possible to realize such a game with Rebol?
The answer is: YES!
First of all you have to know that the technique used with Wolfenstein 3D is called ray casting, you can find a great guide to it here: http://www.permadi.com/tutorial/raycast/index.html
A short explanation is the following:
  • map is only 2D, not 3D, the ray casting give you the effect of 3D
  • map is made of blocks
  • you can walk inside transparent block
  • you can make many steps inside a transparent block (for example 64x64 steps or 1024 x 1024  steps)
  • you can't walk inside visible blocks
  • visible blocks make walls
  • Your view is 90°
  • Your view is divided in 90 rays
  • You view is spread on screen each vertical line is a ray. For example in a screen 360x200, every column of 4 pixels is a ray.
Well the first simple example is taken from the fantastic book of   Olivier Auverlot: "Rebol A programmer’s guide" (you can buy it on www.lulu.com)

Here the commented code:
REBOL [
subject: "raycasting engine"
version: 0.7
]
;note some values are scaled to 1024 to create smooth movements
px: 9 * 1024 ; initial position
py: 11 * 1024 ; initial position
stride: 5 ;step
heading: 0 ;view
turn: 10 ;rotating step

;labirinth map
laby: [
[   8   7   8   7   8   7   8   7   8   7   8   7 ]
[7   0   0   0   0   0   0   0 13   0   0   8 ]
[8   0   0   0   12 0   0   0 14   0   9   7 ]
[7   0   0   0   12 0   4   0 13   0   0   8 ]
[8   0   4   11 11 0   3   0   0   0   0   7 ]
[7   0   3   0   12 3   4   3   4   3   0   8 ]
[8   0   4   0   0   0   3   0   3   0   0   7 ]
[7   0   3   0   0   0   4   0   4   0   9   8 ]
[8   0   4   0   0   0   0   0   0   0   0   7 ]
[7   0   5   6   5   6   0   0   0   0   0   8 ]
[8   0   0   0   0   0   0   0   0   0   0   7 ]
[8   7   8   7   8   7   8   7   8   7   8   7 ]
]
;function to scale the projection of the ray
get-angle: func [v ] [to-integer (((cosine v ) * 1024) / 10)]
;colors
palette: [
    0.0.128
    0.128.0
    0.128.128
    0.0.128
    128.0.128
    128.128.0
    192.192.192
    128.128.128
    0.0.255
    0.255.0
    255.255.0
    0.0.255
    255.0.255
    0.255.255
    255.255.255
    ]
;this will be player view
screen: layout [
    display: box 360x200 effect [
        gradient 0x1 0.0.0 128.128.128
        draw []
        ] edge [
        size: 1x1
        color: 255.255.255
        ]
    ]
;main function to raycasting
retrace: does [
    clear display/effect/draw
    xy1: xy2: 0x0
    angle: remainder (heading - 44) 360 ;this formula put angle between 0 and 359, and since the angle of view (heading) is in the middle of the cone, put the scanning ray at the beggining of the view
    if angle < 0 [ angle: angle + 360 ] ;this make alway positive the angle
    ;starting scanning
    for a angle (angle + 89) 1 [
        ;temporary coordinates
        xx: px
        yy: py
        ;get the ray direction
        stepx: get-angle (a + 90)
        stepy: get-angle a  
       
        l: 0
        until [
            ;walk in the current ray direction in order to find a wall
            xx: xx - stepx
            yy: yy - stepy
            l: l + 1
            column: to-integer (xx / 1024)
            line: to-integer (yy / 1024)
            laby/:line/:column <> 0
            ]
        h: to-integer (900 / l)
        xy1/y: 100 - h
        xy2/y: 100 + h
        xy2/x: xy1/x + 3
        color: pick palette laby/:line/:column
        append display/effect/draw reduce [
            'pen color
            'fill-pen color
            'box xy1 xy2
            ]
        xy1/x: xy2/x + 1
        ]
    show display    
    ]
   
;this function   calculate the new position
player-move: function [/backwards ] [mul ] [
    either backwards [ mul: -1 ] [mul: 1 ]
    newpx: px - ((get-angle (heading + 90)) * stride * mul)
    newpy: py - ((get-angle heading) * stride * mul)
    c: to-integer (newpx / 1024)
    l: to-integer (newpy / 1024)
    if laby/:l/:c = 0 [
        px: newpx
        py: newpy
        retrace
        ]
    ]
;this control when user press keyboard buttons
insert-event-func [
    if (event/type = 'key) [
        switch event/key [
            up [ player-move ]
            down [player-move/backwards ]
            left [
                heading: remainder (heading + (360 - turn)) 360
                retrace
                ]
            right [
                heading: remainder (heading + turn) 360
                retrace
                ]
            ]
        ]
    event  
    ]
retrace
view/title screen join "Raycaster " system/script/header/version


You can see that the labyrinth is a simple array of numbers, if number is zero, than is a transparent block, otherwise is a wall. The number represent the color of the wall.
The main function is the retace function.
I hope that the comments are sufficient clear to understand all the script.
In the next post we'll see how to adding images to the wall.

No comments:

Post a Comment