Raycasting and sprites

General FreeBASIC programming questions.
BasicCoder2
Posts: 3349
Joined: Jan 01, 2009 7:03

Raycasting and sprites

Postby BasicCoder2 » Nov 06, 2012 0:47

This is a cut down example of using raycasting and sprites that could be used in a pseudo 3D doom like game.

As I have no way to provide images in this forum I have added createImages.bas to make 4 crude random colored brick wall textures. Also it produces 1 floor texture and 1 ceiling texture which is assigned to all four floor and ceiling textures. The simple sprite image used by the four example sprites is generated in the loadSprite() subroutine.

I cut out the extra code to move animated sprites around and provide different viewpoints as the problem I was trying to solve was simply how to display a visible sprite when it was partly covered by a wall. I did this by coding the floor data with &H22000000. If the bottom pixel is floor data than a vertical line of the sprite will be visible.

createImages.bas

Code: Select all

screenres 64,64,32

dim shared as ubyte r,g,b

sub getRandomColor()
    r = int(rnd(1)*200)
    g = int(rnd(1)*200)
    b = int(rnd(1)*200)
end sub
 
'draw brick wall images
for k as integer = 0 to 3
    getRandomColor()
    line (0,0)-(63,63),rgb(r,g,b),bf

    getRandomColor()
    for j as integer = 0 to 63 step 8
        line (0,j)-(63,j),rgb(r,g,b)
    next j

    for i as integer = 0 to 63 step 16
        for j as integer = 0 to 63 step 16
            line (i,j)-(i,j+8),rgb(r,g,b)
        next j
    next i

    for i as integer = 8 to 63 step 16
        for j as integer = 8 to 63 step 16
            line (i,j)-(i,j+8),rgb(r,g,b)
        next j
    next i

    bsave "wall"+ str(k) + ".bmp",0
    sleep
next k


sleep

'draw tiled floor image
line (0,0)-(63,63),rgb(100,80,100),bf

for j as integer = 0 to 63 step 8
    for i as integer = 0 to 63 step 8
        line (i,j)-(i+6,j+6),rgb(50,10,50),b
    next i
next j
line (0,31)-(63,31),rgb(255,0,0)
line (31,0)-(31,63),rgb(255,0,0)
bsave "floor.bmp",0
sleep

'draw ceiling image
line (0,0)-(63,63),rgb(40,80,70),bf

for j as integer = 6 to 63 step 16
    for i as integer = 6 to 63 step 16
        circle (i,j),4,rgb(200,200,0),,,,f
    next i
next j
bsave "ceiling.bmp",0
sleep
end

demo code

Code: Select all

'some useful defines
Const Pi = 4 * Atn(1)
Dim Shared As Double TwoPi = 8 * Atn(1)
Dim Shared As Double RtoD = 180 / Pi   ' radians * RtoD = degrees
Dim Shared As Double DtoR = Pi / 180   ' degrees * DtoR = radians

'variables of casting ray
Dim Shared As Double rAngle            ' angle of ray
dim shared as double sAngle            ' angle to sprite
dim shared as double mult,min,max
Dim Shared As Double dx,dy             ' some working variables


Dim Shared As Double oAngle            ' observer angle in radians
dim shared as double angle             ' observer angle in degrees
Dim shared As Double ox,oy,mv          ' observer position and velocity
dim shared as integer pd               ' direction of observer

dim shared as integer bSize = 32       ' size of map display

dim shared as double sx,sy             ' position of sprite

Dim Shared As Double range             'range to scan -range to +range
Dim Shared As Double w                 'position along the range values

Dim Shared As Integer floor(64,64,4)     ' 4 floor textures
Dim Shared As Integer ceiling(64,64,4)   ' 4 ceiling textures
Dim Shared As Integer wall(64,64,4)      ' 4 wall textures


type SPRITE
    as double   px
    as double   py              ' position of sprite in world coordinates
    as uinteger img(64,64)      ' holds image of sprite
end type



'------------------------------ ***
dim shared as SPRITE sprites(4)  ' 4 sprites

'initialize sprites positions


'sprite world coordinates
sprites(0).px =  7 * 64+31
sprites(0).py =  1 * 64+31
sprites(1).px = 13 * 64+31
sprites(1).py = 13 * 64+31
sprites(2).px =  5 * 64+31
sprites(2).py =  9 * 64+31
sprites(3).px = 10 * 64+31
sprites(3).py = 12 * 64+31


'------------------------------- ***

'size of world
const DungeonX = 16   '16 x 16 squares 64x64 pixels in size
const DungeonY = 16


dim shared as integer dungeon(DungeonX,DungeonY)

for j as integer = 0 to DungeonY-1
    for i as integer = 0 to DungeonX-1
        read dungeon(i,j)
    next i
next j


'=================================================

Screenres 1024,512,32,2
screenset 1,0   'select page

'===================================================

sub loadSprites()
    line (0,0)-(63,63),rgb(255,0,255),bf 'used as transparent
    circle (30,10),10,rgb(100,30,0),,,,f
    circle (30,40),20,rgb(30,30,255),,,,f

    for k as integer = 0 to 3
        for j as integer = 0 to 63
            for i as integer = 0 to 63
                sprites(k).img(i,j)=point(i,j)
                pset(i,j),rgb(0,255,0)
            next i
        next j
    next k
end sub

sub loadWallCeilingFloorTextures()
    'save bitmap into wall
    for k as integer = 0 to 3
        bload "wall" + str(k) + ".bmp"
        For j As Integer = 0 To 63
            For i As Integer = 0 To 63
                wall(i,j,k) = Point(i,j)
            Next i
        Next j
    next k

    'bload "c:\freebasic\bitmaps\RasterData\bFloor.bmp"
    bload "floor.bmp"
    for k as integer = 0 to 3
        For j As Integer = 0 To 63
            For i As Integer = 0 To 63
                'floor(i,j,k)=Point(i,j)
                floor(i,j,k) = &H22000000 or (Point(i,j) and &H00FFFFFF) 'texture 0
            Next i
        Next j
    next k

    'bload "c:\freebasic\bitmaps\RasterData\bCeiling.bmp"
    bload "ceiling.bmp"
    'bload "floor.bmp"
    for k as integer = 0 to 3
        For j As Integer = 0 To 63
            For i As Integer = 0 To 63
                ceiling(i,j,k) = Point(i,j)
            Next i
        Next j
    next k
end sub

''============================================================
'' This shoots ray from observer drawing the floor and ceiling
'' until it reaches a wall block.
''============================================================
sub shootRay(x1 As Integer,y1 As Integer,angle As Double)

    dim as integer  LocX,LocY
    dim as integer  xx,yy
    dim as uinteger worldData = 0  'holds wall number and x-coordinates
    dim as double   mult,min,max
    dim as integer  x_coord,sLine
    Dim As Double   dx,dy,change,aa,x,y
    Dim As Double   distance
    Dim As Double   oldDistance

    dx = Cos(angle)
    dy = Sin(angle)
    y = y1
    x = x1
    'compute and pset floor, ceiling and ray data
    distance = Sqr( Abs(x1-x)^2 + Abs(y1-y)^2)
    'adjust distance value
    distance = distance * Cos(rAngle-oAngle)
    oldDistance = distance
    If Abs(dx)>abs(dy) Then
        change = Abs(dy)/Abs(dx)
        If dx<0 Then
            aa = -1
        Else
            aa =  1
        End If
        If dy<0 Then
            change = - change
        End If
        LocX = x\64
        LocY = y\64

        While dungeon(LocX,LocY)= 0  'no wall = 0
           
            x = x + aa
            y = y + change

            '==================================================
            'compute and pset floor, ceiling and ray data
            distance = Sqr( Abs(x1-x)^2 + Abs(y1-y)^2)
            'adjust distance value
            distance = distance * Cos(rAngle-oAngle)
            xx = x-(xx\64)*64
            yy = y-(yy\64)*64
            xx = xx and &H3F
            yy = yy and &H3F
            pset(x\2,y\2),rgb(255,100,100) 'draws ray on map display
            Line(w+512+range,256+(6000/distance))-(w+512+range,256+(6000/oldDistance)),floor(xx,yy,0)
            Line(w+512+range,256-(6000/distance))-(w+512+range,256-(6000/oldDistance)),ceiling(xx,yy,0)
           
            oldDistance = distance
            '===================================================
            LocX = x\64
            LocY = y\64
           
        Wend
    Else
        change = Abs(dx)/Abs(dy)
        If dy<0 Then
            aa = -1
        Else
            aa = 1
        End If
        If dx<0 Then
            change = -change
        End If
        LocX = x\64
        LocY = y\64

        While dungeon(LocX,LocY) = 0  'while still on floor

            y = y + aa
            x = x + change
            '=================================================
            'compute and pset floor, ceiling and ray data
            distance = Sqr( Abs(x1-x)^2 + Abs(y1-y)^2)
           'adjust distance value
            distance = distance * Cos(rAngle-oAngle)
            xx = x-(xx\64)*64  'compute pixel in image
            yy = y-(yy\64)*64
            xx = xx and &H3F
            yy = yy and &H3F
           
            pset(x\2,y\2),rgb(255,100,100)  'draws ray on map display

            Line(w+512+range,256+(6000/distance))-(w+512+range,256+(6000/oldDistance)),floor(xx,yy,0)
            Line(w+512+range,256-(6000/distance))-(w+512+range,256-(6000/oldDistance)),ceiling(xx,yy,0)
           
            oldDistance = distance
            '==============================================
            LocX = x\64
            LocY = y\64
        Wend       
    End If
   
    dx = Abs(x-x1)
    dy = Abs(y-y1)
    distance = Sqr(dx*dx+dy*dy)
    'adjust distance value
    distance = distance * Cos(rAngle-oAngle)
       
    max = (6000/distance) * 2 'screen height of wall strip
    min = 64  'height of wall
    mult = min/max  'conversion of screen y position to wall y position
       
    'extract x-coord of wall image
    xx = x-(x\64)*64
    yy = y-(y\64)*64
    if xx = 0 or xx = 63 then
        x_coord = yy
    end if
    if yy = 0 or yy = 63 then
        x_coord = xx
    end if


    sLine = 256-(6000/distance)  'screen start of vertical line of wall
    'draw wall
    dim as integer k
    k = Dungeon(LocX,LocY)-1  'get wall number
    for y as integer = 0 to max
        pset(w+512+range,y+sLine),wall(x_coord,y*mult,k)
    next y
   
End sub

sub Draw2DMap()
    Cls
    'draw map on screen
    for j as integer = 0 to DungeonY-1
        for i as integer = 0 to DungeonX-1
           
            'WALLS AND FLOOR
            if dungeon(i,j)>0 then
                line (i*bSize,j*bSize)-(i*bSize+bSize-1,j*bSize+bSize-1),rgb(120,120,120),bf
            else
                line (i*bSize,j*bSize)-(i*bSize+bSize-1,j*bSize+bSize-1),rgb(255,255,255),bf
            end if
            line (i*bSize,j*bSize)-(i*bSize+bSize-1,j*bSize+bSize-1),rgb(0,0,0),b           
            'draw position of sprites
            for k as integer = 0 to 3
                circle (sprites(k).px\2,sprites(k).py\2),5,rgb(0,0,255),,,,f
            next k

        next i
    next j

end sub

sub readInput()
    '=========   USER INPUT =========================
    'Check arrow keys and update position accordingly
    Dim As Integer velocity
    mv = 0 'set velocity to zero
    If MULTIKEY(&h4B) Then oAngle = oAngle - 6 * DtoR  'angles in radians
    If MULTIKEY(&h4D) Then oAngle = oAngle + 6 * DtoR
    If oAngle > TwoPi Then oAngle = oAngle - TwoPi
    If oAngle < 0 Then oAngle = oAngle + TwoPi

    'find quandrants to compare with sprites directions
    angle = oAngle*RtoD
    pd = 1  'east
    if angle > 45 and angle < 135 then
        pd = 2  'south
    end if
    if angle >=135 and angle <225 then
        pd = 3  'west
    end if
    if angle >=225 and angle <315 then
        pd = 0  'north
    end if
   
    If MULTIKEY(&h48) Then mv =  8  'move forward
    If MULTIKEY(&h50) Then mv = -8  'move back
    dx = Cos(oAngle) * mv
    dy = Sin(oAngle) * mv
    ox = ox + dx
    oy = oy + dy
    'inside wall?
    if Dungeon(ox\64,oy\64)>0 then
        ox = ox-dx
        oy = oy-dy
    end if
end sub

sub drawBackGround()
    range = 260 'fits 512 width -260 to +260   
    'DRAW FLOOR CEILING AND WALL
    For w  = -range To +range
        rAngle = atan2(w,range) + oAngle
        If rAngle > TwoPi Then rAngle = rAngle - TwoPi
        If rAngle < 0 Then rAngle = rAngle + TwoPi
        shootRay(ox,oy,rAngle)
    Next w
end sub

sub drawSprites()
    dim as double distance,distance2,spriteX,spriteY
   
    'DRAW ANY SPRITES IN VIEW
    for k as integer = 0 to 3

        sx = sprites(k).px
        sy = sprites(k).py
       
        distance = Sqr( Abs(sx-ox)^2 + Abs(sy-oy)^2) 'actual distance from observer
        sAngle = atan2(sy-oy, sx-ox)
        If sAngle > TwoPi Then sAngle = sAngle - TwoPi
        If sAngle < 0 Then sAngle = sAngle + TwoPi

        distance = distance * Cos(sAngle-oAngle)  'adjusted distance for 3D display
        w = range * tan(sAngle - oAngle)
        spriteX = w+512+range        '512 is width of 3D display
        spriteY = 256+6000/distance  '256 is half of 512
   
        'draw sprite on 3D screen display if within view

        max = (6000/distance) * 2  'screen height
        min = 63  'size of sprite - 1
        mult = min/max  'conversion of coordinates from screen coord to sprite coord
       
        '**************  DRAW SPRITES(k) ********************
        for x as integer = 1 to max
            if (point(x+spriteX-max/2,(max)+spriteY-max) and &HFF000000) = &H22000000 then 'check if vertical slice visible
                for y as integer = max to 1 step -1
                    if sprites(k).img(x*mult,y*mult)<>rgb(255,0,255) then
                        pset(x+spriteX-max/2,y+spriteY-max), sprites(k).img(x*mult,y*mult)
                    end if
                next y
            end if
        next x
        '************************************************
    next k
   
end sub

'==================
'initialize game
'==================
loadSprites()
loadWallCeilingFloorTextures()

ox = 827
oy = 95
mv = 0
oAngle = 0 * DtoR  'angle in radians



'==========   MAIN GAME LOOP =====================

dim as string compass

Do
    draw2DMap()
    drawBackGround()
    drawSprites()
    screencopy         'display result of drawings
    readInput()
Loop While Not MULTIKEY(&h1)

End

' =0 floor
' >0 wall  (1,2,3 or 4)

data  2,2,3,4,3,3,2,4,4,4,3,3,1,3,1,2
data  1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,4
data  4,0,4,2,0,2,0,2,2,0,3,4,2,0,1,3
data  2,0,0,2,0,2,0,2,0,0,4,0,0,0,0,2
data  2,3,4,0,0,2,0,2,3,0,0,0,4,2,3,3
data  3,0,0,0,3,0,4,4,0,0,4,4,4,0,0,2
data  4,0,1,0,0,0,3,2,0,1,0,0,0,4,0,1
data  3,2,4,0,4,3,3,1,0,0,0,3,0,0,0,2
data  4,4,1,0,2,2,4,4,1,3,1,2,0,2,3,1
data  2,0,0,0,0,0,0,0,0,2,0,0,0,1,0,4
data  4,0,1,3,0,1,0,3,0,4,3,3,0,0,0,4
data  4,0,0,2,0,4,0,4,0,0,0,0,0,1,1,3
data  2,3,4,0,0,3,0,2,4,3,0,2,4,4,3,2
data  2,0,0,0,4,0,3,2,0,0,0,0,0,0,0,3
data  4,0,2,0,0,0,1,3,2,0,3,0,4,0,3,1
data  4,3,4,4,2,1,4,2,4,3,2,2,4,3,1,4


albert
Posts: 4777
Joined: Sep 28, 2006 2:41
Location: California, USA

Re: Raycasting and sprites

Postby albert » Nov 14, 2012 3:43

Thats awesome BasicCoder2 !!!!
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Raycasting and sprites

Postby MichaelW » Nov 14, 2012 7:13

Yes, very impressive for such a small amount of code. Modifying the main loop to display FPS in the title bar, and assuming that I stay away from the walls, I get ~~7 on my 3.0GHz P4 (Northwood) system, with or without -fpu SSE. Almost all of the delay is in the call to drawBackGround, and commenting it out increased the FPS to ~~150.

Return to “General”

Who is online

Users browsing this forum: No registered users and 3 guests