Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
1990's 3D Doom-Like Walls Example
#6
@SierraKen I wanted to mess with raycasters for a long time. And thanks to, I had a bit of fun with this.

I re-wrote this (almost entirely) for QB64-PE v4. Hopefully it will be easy to understand even without comments.

It currently only renders solid walls and brute forces it's way.


[Image: Screenshot-2025-01-12-024619.png]


Code: (Select All)
' Solid-wall raycaster by a740g
' Does not use DDA-based optimizations (yet)
' Performance may suffer on low-end / old hardware

_DEFINE A-Z AS LONG
OPTION _EXPLICIT

CONST SCREEN_WIDTH& = 640&
CONST SCREEN_HEIGHT& = 400&
CONST SCREEN_HALF_WIDTH& = SCREEN_WIDTH \ 2&
CONST SCREEN_HALF_HEIGHT& = SCREEN_HEIGHT \ 2&
CONST SCREEN_MODE& = 256&
CONST RENDER_FPS& = 60&
CONST PLAYER_FOV& = 60&
CONST PLAYER_HALF_FOV& = PLAYER_FOV \ 2&
CONST RAYCAST_INCREMENT_ANGLE! = PLAYER_FOV / SCREEN_WIDTH
CONST RAYCAST_PRECISION! = 64!
CONST MAP_DEFAULT_BOUNDARY_COLOR~& = 7
CONST PLAYER_MOVE_SPEED! = 0.1!
CONST PLAYER_LOOK_SPEED! = 0.1!
CONST AUTOMAP_SCALE& = 4
CONST AUTOMAP_PLAYER_RADIUS& = 2
CONST AUTOMAP_PLAYER_COLOR~& = 15
CONST AUTOMAP_PLAYER_CAMERA_COLOR~& = 14

TYPE Vector2i
    x AS LONG
    y AS LONG
END TYPE

TYPE Vector2f
    x AS SINGLE
    y AS SINGLE
END TYPE

TYPE Camera
    angle AS SINGLE
    direction AS Vector2f
END TYPE

TYPE Player
    position AS Vector2f
    camera AS Camera
END TYPE

RANDOMIZE TIMER

$RESIZE:SMOOTH
SCREEN _NEWIMAGE(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_MODE)
_ALLOWFULLSCREEN _SQUAREPIXELS , _SMOOTH

REDIM map(0, 0) AS _UNSIGNED LONG
RESTORE level_data
MakeWorld map()

DIM environment AS LONG
environment = MakeEnvironment

DIM player AS Player
player.position.x = 6!
player.position.y = 3!
player.camera.angle = 90!
UpdatePlayerCamera player

DIM automap AS LONG
automap = MakeAutomap(map())

_MOUSEHIDE

DO
    HandleInput player, map()
    RenderFrame player, map(), environment, automap

    _DISPLAY
    _LIMIT RENDER_FPS
LOOP UNTIL _KEYHIT = 27

SYSTEM

' Level data:
' Width,Height
' Wall colors in hexadecimal
level_data:
DATA 24,20
DATA 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
DATA 9,0,0,C,0,0,0,0,0,5,0,9,0,0,0,0,0,0,0,0,0,0,0,9
DATA 9,0,0,C,0,0,0,0,0,D,0,9,0,0,C,0,0,0,0,0,0,0,0,9
DATA 9,0,0,C,0,0,0,0,0,5,0,0,0,0,C,0,0,0,0,0,0,D,0,9
DATA 9,0,0,C,0,0,0,0,0,D,0,0,0,0,C,0,0,0,0,0,0,D,0,9
DATA 9,0,0,0,0,1,9,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,9
DATA 9,0,0,0,0,9,1,0,0,0,0,9,0,0,0,0,0,1,1,0,0,0,0,9
DATA 9,0,C,0,0,0,0,0,0,0,0,0,0,0,C,0,0,0,0,0,0,0,0,9
DATA 9,0,C,0,0,0,0,0,B,B,0,0,0,0,C,0,0,0,0,0,B,B,0,9
DATA 9,0,C,0,0,0,0,0,B,B,0,0,0,0,C,0,0,0,0,0,B,B,0,9
DATA 9,0,4,0,0,0,0,0,0,0,0,0,0,0,1,9,1,9,1,9,1,9,1,9
DATA 9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9
DATA 9,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,9
DATA 9,0,1,0,0,0,5,0,0,0,0,0,0,0,1,0,0,0,0,0,0,5,0,9
DATA 9,0,2,0,0,4,0,0,0,0,0,9,0,0,2,0,0,0,0,0,0,0,0,9
DATA 9,0,B,0,0,0,0,0,0,0,0,9,0,0,B,0,0,0,0,0,0,0,0,9
DATA 9,0,0,0,0,7,8,0,0,0,0,0,0,0,0,0,0,7,8,0,0,0,0,9
DATA 9,0,5,0,0,8,7,0,0,0,0,0,0,0,5,0,0,8,7,0,0,0,0,9
DATA 9,0,6,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,9
DATA 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9

SUB MakeWorld (map( ,) AS _UNSIGNED LONG)
    DIM m AS Vector2i
    READ m.x, m.y
    REDIM map(0 TO m.x - 1, 0 TO m.y - 1) AS _UNSIGNED LONG

    DIM d AS STRING

    FOR m.y = 0 TO UBOUND(map, 2)
        FOR m.x = 0 TO UBOUND(map, 1)
            READ d
            map(m.x, m.y) = VAL("&H" + d)
        NEXT m.x
    NEXT m.y
END SUB

FUNCTION MakeEnvironment&
    CONST PANORAMA_WIDTH& = SCREEN_WIDTH * 4
    CONST SUN_RADIUS& = 20
    CONST SUN_COLOR& = 14
    CONST CLOUD_COUNT& = 10
    CONST CLOUD_COLOR& = 15
    CONST BUILDING_COLOR& = 8

    DIM oldDest AS LONG: oldDest = _DEST
    DIM img AS LONG: img = _NEWIMAGE(PANORAMA_WIDTH, SCREEN_HEIGHT, SCREEN_MODE)
    _DEST img

    LINE (0, 0)-(PANORAMA_WIDTH - 1, SCREEN_HALF_HEIGHT - 1), 3, BF

    DIM x AS LONG: x = SUN_RADIUS + (PANORAMA_WIDTH - 2 * SUN_RADIUS - 1) * RND
    DIM y AS LONG: y = SUN_RADIUS + (SCREEN_HALF_HEIGHT - 2 * SUN_RADIUS - 1) * RND
    CIRCLE (x, y), SUN_RADIUS, SUN_COLOR
    PAINT (x, y), SUN_COLOR, SUN_COLOR

    DIM AS LONG i, j

    FOR i = 1 TO CLOUD_COUNT
        j = (PANORAMA_WIDTH \ 8) * RND
        x = j + (PANORAMA_WIDTH - 2 * j) * RND
        y = j + (SCREEN_HALF_HEIGHT - 2 * j) * RND
        CIRCLE (x, y), j, CLOUD_COLOR, , , RND / 10!
        PAINT (x, y), CLOUD_COLOR
    NEXT i

    x = (PANORAMA_WIDTH - 80) * RND

    LINE (x, 40)-(x + 40, 30), BUILDING_COLOR
    LINE (x + 40, 30)-(x + 80, 40), BUILDING_COLOR
    LINE (x, 40)-(x, SCREEN_HALF_HEIGHT - 1), BUILDING_COLOR
    LINE (x + 80, 40)-(x + 80, SCREEN_HALF_HEIGHT - 1), BUILDING_COLOR
    LINE (x, SCREEN_HALF_HEIGHT - 1)-(x + 80, SCREEN_HALF_HEIGHT - 1), BUILDING_COLOR
    PAINT (x + 24, 100), BUILDING_COLOR

    FOR i = 1 TO 20
        PSET (2 + x + RND * 76, 40 + RND * 160), SUN_COLOR
    NEXT i

    LINE (x, 40)-(x + 40, 30), 0
    LINE (x + 40, 30)-(x + 80, 40), 0
    LINE (x + 38, 30)-(x + 38, SCREEN_HALF_HEIGHT - 1), 0
    LINE (x, 40)-(x, SCREEN_HALF_HEIGHT - 1), 0
    LINE (x + 80, 40)-(x + 80, SCREEN_HALF_HEIGHT - 1), 0

    FOR i = SCREEN_HALF_HEIGHT TO SCREEN_HEIGHT - 1
        LINE (0, i)-(PANORAMA_WIDTH, i), 6, , _IIF(i AND 1, &B1010101010101010, &B0101010101010101)
        LINE (0, i)-(PANORAMA_WIDTH, i), 8, , _IIF(i AND 1, &B0101010101010101, &B1010101010101010)
    NEXT i

    _DEST oldDest

    MakeEnvironment = img
END FUNCTION

SUB UpdatePlayerCamera (player AS Player)
    DIM ra AS SINGLE: ra = _D2R(player.camera.angle)
    player.camera.direction.x = COS(ra)
    player.camera.direction.y = -SIN(ra)
END SUB

SUB HandleInput (player AS Player, map( ,) AS _UNSIGNED LONG)
    DIM mouseUsed AS _BYTE

    DIM m AS Vector2i
    WHILE _MOUSEINPUT
        m.x = m.x + _MOUSEMOVEMENTX
        m.y = m.y + _MOUSEMOVEMENTY
        mouseUsed = _TRUE
    WEND

    _MOUSEMOVE SCREEN_HALF_WIDTH, SCREEN_HALF_HEIGHT

    IF mouseUsed THEN
        player.camera.angle = (player.camera.angle + m.x * PLAYER_LOOK_SPEED)
        IF player.camera.angle >= 360! THEN player.camera.angle = player.camera.angle - 360!
        IF player.camera.angle < 0! THEN player.camera.angle = player.camera.angle + 360!
        UpdatePlayerCamera player
    END IF

    DIM keyboardUsed AS _BYTE, position AS Vector2f

    IF _KEYDOWN(87) _ORELSE _KEYDOWN(119) THEN
        position.x = player.position.x + player.camera.direction.x * PLAYER_MOVE_SPEED
        position.y = player.position.y + player.camera.direction.y * PLAYER_MOVE_SPEED
        keyboardUsed = _TRUE
    END IF

    IF _KEYDOWN(83) _ORELSE _KEYDOWN(115) THEN
        position.x = player.position.x - player.camera.direction.x * PLAYER_MOVE_SPEED
        position.y = player.position.y - player.camera.direction.y * PLAYER_MOVE_SPEED
        keyboardUsed = _TRUE
    END IF

    IF _KEYDOWN(65) _ORELSE _KEYDOWN(97) THEN
        position.x = player.position.x - player.camera.direction.y * PLAYER_MOVE_SPEED
        position.y = player.position.y + player.camera.direction.x * PLAYER_MOVE_SPEED
        keyboardUsed = _TRUE
    END IF

    IF _KEYDOWN(68) _ORELSE _KEYDOWN(100) THEN
        position.x = player.position.x + player.camera.direction.y * PLAYER_MOVE_SPEED
        position.y = player.position.y - player.camera.direction.x * PLAYER_MOVE_SPEED
        keyboardUsed = _TRUE
    END IF

    IF keyboardUsed THEN
        m.x = position.x
        m.y = position.y

        IF m.x >= 0 _ANDALSO m.y >= 0 _ANDALSO m.x <= UBOUND(map, 1) _ANDALSO m.y <= UBOUND(map, 2) _ANDALSO map(m.x, m.y) = 0 THEN
            player.position = position
        END IF
    END IF
END SUB

FUNCTION MakeAutomap& (map( ,) AS _UNSIGNED LONG)
    DIM img AS LONG: img = _NEWIMAGE((UBOUND(map, 1) + 1) * AUTOMAP_SCALE, (UBOUND(map, 2) + 1) * AUTOMAP_SCALE, SCREEN_MODE)
    _CLEARCOLOR 0, img
    MakeAutomap = img
END FUNCTION

SUB DrawAutomap (player AS Player, map( ,) AS _UNSIGNED LONG, automapImg AS LONG)
    DIM oldDest AS LONG: oldDest = _DEST
    _DEST automapImg

    CLS

    DIM AS Vector2i p, o
    DIM mapHeight AS LONG: mapHeight = UBOUND(map, 2)

    FOR p.y = 0 TO mapHeight
        FOR p.x = 0 TO UBOUND(map, 1)
            o.x = p.x * AUTOMAP_SCALE
            o.y = (mapHeight - p.y) * AUTOMAP_SCALE
            LINE (o.x + 1, o.y + 1)-(o.x + AUTOMAP_SCALE - 1, o.y + AUTOMAP_SCALE - 1), map(p.x, p.y), BF
            LINE (o.x, o.y)-(o.x + AUTOMAP_SCALE, o.y + AUTOMAP_SCALE), 7, B
        NEXT p.x
    NEXT p.y

    p.x = AUTOMAP_PLAYER_RADIUS + player.position.x * AUTOMAP_SCALE
    p.y = AUTOMAP_PLAYER_RADIUS + (mapHeight - player.position.y) * AUTOMAP_SCALE
    o.x = p.x + player.camera.direction.x * AUTOMAP_SCALE
    o.y = p.y - player.camera.direction.y * AUTOMAP_SCALE

    CIRCLE (p.x, p.y), AUTOMAP_PLAYER_RADIUS, AUTOMAP_PLAYER_COLOR
    LINE (p.x, p.y)-(o.x, o.y), AUTOMAP_PLAYER_CAMERA_COLOR

    _DEST oldDest

    _PUTIMAGE (0, 0), automapImg
END SUB

SUB DrawBackground (player AS Player, environmentImg AS LONG)
    DIM srcX AS LONG: srcX = (player.camera.angle / 360!) * _WIDTH(environmentImg)

    IF srcX + SCREEN_WIDTH > _WIDTH(environmentImg) THEN
        DIM partialWidth AS LONG: partialWidth = _WIDTH(environmentImg) - srcX

        _PUTIMAGE (0, 0)-(partialWidth - 1, SCREEN_HEIGHT - 1), environmentImg, , (srcX, 0)-(_WIDTH(environmentImg) - 1, _HEIGHT(environmentImg) - 1)
        _PUTIMAGE (partialWidth, 0)-(SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1), environmentImg, , (0, 0)-(SCREEN_WIDTH - partialWidth - 1, _HEIGHT(environmentImg) - 1)
    ELSE
        _PUTIMAGE (0, 0)-(SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1), environmentImg, , (srcX, 0)-(srcX + SCREEN_WIDTH - 1, _HEIGHT(environmentImg) - 1)
    END IF
END SUB


SUB RenderFrame (player AS Player, map( ,) AS _UNSIGNED LONG, environmentImg AS LONG, automapImg AS LONG)
    DrawBackground player, environmentImg

    DIM rayAngle AS SINGLE
    rayAngle = player.camera.angle - PLAYER_HALF_FOV
    IF rayAngle < 0! THEN rayAngle = rayAngle + 360!

    DIM i AS LONG, wallColor AS _UNSIGNED LONG, wallHeight AS LONG
    DIM AS Vector2f rayPosition, rayDirection
    DIM rayAngleRadian AS SINGLE, mapPosition AS Vector2i, distance AS SINGLE

    FOR i = 0 TO SCREEN_WIDTH - 1
        rayAngleRadian = _D2R(rayAngle)
        rayDirection.x = COS(rayAngleRadian) / RAYCAST_PRECISION
        rayDirection.y = -SIN(rayAngleRadian) / RAYCAST_PRECISION
        rayPosition = player.position

        DO
            rayPosition.x = rayPosition.x + rayDirection.x
            rayPosition.y = rayPosition.y + rayDirection.y
            mapPosition.x = rayPosition.x
            mapPosition.y = rayPosition.y

            IF mapPosition.x < 0 _ORELSE mapPosition.y < 0 _ORELSE mapPosition.x > UBOUND(map, 1) _ORELSE mapPosition.y > UBOUND(map, 2) THEN
                wallColor = MAP_DEFAULT_BOUNDARY_COLOR
                EXIT DO
            END IF

            wallColor = map(mapPosition.x, mapPosition.y)
        LOOP WHILE wallColor = 0

        IF wallColor THEN
            distance = SQR((player.position.x - rayPosition.x) ^ 2! + (player.position.y - rayPosition.y) ^ 2!) * COS(_D2R(player.camera.angle - rayAngle))
            wallHeight = SCREEN_HALF_HEIGHT / distance
            LINE (i, SCREEN_HALF_HEIGHT - wallHeight)-(i, SCREEN_HALF_HEIGHT + wallHeight), wallColor
        END IF

        rayAngle = rayAngle + RAYCAST_INCREMENT_ANGLE
        IF rayAngle >= 360! THEN rayAngle = rayAngle - 360!
    NEXT i

    DrawAutomap player, map(), automapImg
END SUB

Next stop, DDA, texturemapping and voxelspace.  Big Grin
Reply


Messages In This Thread
1990's 3D Doom-Like Walls Example - by SierraKen - 01-06-2025, 06:40 AM
RE: 1990's 3D Doom-Like Walls Example - by Petr - 01-06-2025, 05:40 PM
RE: 1990's 3D Doom-Like Walls Example - by a740g - 01-11-2025, 09:31 PM



Users browsing this thread: MasterGy, 2 Guest(s)