Where bored and combined gar Benjamins and my own old concepts for a raycaster in agk.
I use sprites instead to give better performance and control over the rendering.
No textures or sprites in engine but might add it later when i have time.
If you find the source fits anywhere else in the forums feel free to add it there.
If you add things so please share for others to learn by as i do here.
Promo new agk users to advance in there skills.
//-----------------------------------------------------------------------------------
// Project: Simple Raycaster based heavly on "3D Maze" Blitz Basic program by jfk
// Blitz Basic code here: http://www.blitzbasic.com/codearcs/codearcs.php?code=579
//-----------------------------------------------------------------------------------
// Gar original code: https://forum.thegamecreators.com/thread/218529
// Created: 2016-12-30 by gar benjamin
//-----------------------------------------------------------------------------------
// Enhanced for agk studio in 2019-09-15 by cliff mellangÄrd aka play8bitars
//-----------------------------------------------------------------------------------
// Gameboy resolution 166x144
//-----------------------------------------------------------------------------------
// Display and map rendering constants
#constant SCREEN_WIDTH = 1024*2
#constant SCREEN_HEIGHT = 720*2
#constant MAP_WIDTH = 20
#constant MAP_HEIGHT = 15
//-----------------------------------------------------------------------------------
Type sRayCast
Window_X as integer
Window_Y as integer
Column_Width as float
Columns as integer
Resolution_X as integer
Resolution_Y as integer
Distance as integer
Render_Counter as integer
Render_Counter_Max as integer
EndType
Global RayCast as sRayCast
RayCast.Render_Counter_Max=1 // skip raycasting this many cycles 0=no skip
//-----------------------------------------------------------------------------------
// position raycaster window
//-----------------------------------------------------------------------------------
RayCast.Window_X=128
RayCast.Window_Y=0
//-----------------------------------------------------------------------------------
// Is pretty much the virtual resolution and can be set as you please usually
//-----------------------------------------------------------------------------------
RayCast.Resolution_X=SCREEN_WIDTH/2
RayCast.Resolution_Y=SCREEN_HEIGHT/2.2
//-----------------------------------------------------------------------------------
// This part increases performance by cutting down screen columns that draw walls.
// Also sets the width of the column to still make it nice
//-----------------------------------------------------------------------------------
// must be dividable with 2!
//-----------------------------------------------------------------------------------
RayCast.Columns=RayCast.Resolution_X/4
RayCast.Column_Width=(RayCast.Resolution_X/RayCast.Columns)/1.4
Raycast.Distance=400
//-----------------------------------------------------------------------------------
// Window and sync settings
//-----------------------------------------------------------------------------------
// show all errors
SetErrorMode(2)
SetPrintSize(18)
SetWindowTitle("App game kit! Solid Color Raycaster Template")
SetWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT, 0,1)
// set display properties
SetVirtualResolution(RayCast.Resolution_X, RayCast.Resolution_Y)
SetOrientationAllowed(0, 0, 1, 1)
SetSyncRate(30, 0) // best for android and html5
//SetVSync(1) // Best for windows and linux
//-----------------------------------------------------------------------------------
// keyboard constants
//-----------------------------------------------------------------------------------
#constant KEY_A = 65
#constant KEY_LEFT = 37
#constant KEY_D = 68
#constant KEY_RIGHT = 39
#constant KEY_X = 88
#constant KEY_SPACE = 32
#constant KEY_RSHFT = 16
#constant KEY_ENTER = 13
#constant KEY_UP = 38
#constant KEY_W = 87
#constant KEY_DOWN = 40
#constant KEY_S = 83
//-----------------------------------------------------------------------------------
global map as integer[20, 20]
BuildMap()
//-----------------------------------------------------------------------------------
global playerXpos as float
global playerZpos as float
global playerAngleFacing as float
global pxold as float
global pzold as float
//-----------------------------------------------------------------------------------
// starting position and starting angle player is facing
playerXpos = 13.5
playerZpos = 1.5
playerAngleFacing = 0
//-----------------------------------------------------------------------------------
// create screen columns
//-----------------------------------------------------------------------------------
x=RayCast.Window_x
y=(RayCast.Resolution_Y/2)+RayCast.Window_Y
width=0
for column=1 to RayCast.Columns
createsprite(column,0)
setspriteposition(column,x,y)
inc x,RayCast.Column_Width
width=width+RayCast.Column_Width
next column
//-----------------------------------------------------------------------------------
global floor
floor=createsprite(0)
setspritesize(floor,width,(RayCast.Resolution_Y/2)-16)
setspriteposition(floor,RayCast.Window_x,RayCast.Window_Y+(RayCast.Resolution_Y/2)+16)
setspritecolor(floor,50,50,100,255)
setspritedepth(floor,100)
Global ceiling
ceiling=createsprite(0)
setspritesize(ceiling,width,(RayCast.Resolution_Y/2)-16)
setspriteposition(ceiling,RayCast.Window_x,RayCast.Window_Y)
setspritecolor(ceiling,25,25,50,255)
setspritedepth(ceiling,100)
Global GFR as float
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
do
PlayerUpdate()
if RayCast.Render_Counter=0 then RenderMapView()
if getrawkeyreleased(49) then dec RayCast.Render_Counter_Max
if getrawkeyreleased(50) then inc RayCast.Render_Counter_Max
print("Html5 sprite based Raycaster Prototype (hardcoded to work well on low fps)")
print("Mouse button to move raycast window")
print(str(RayCast.Render_Counter_Max)+" 1-2 inc and decrease raycast skip.")
print("columns:"+str(RayCast.Columns))
print("Raycast distance:"+str(RayCast.Distance))
print("X:"+str(playerXpos))
print("Z:"+str(playerZpos))
print("Ang:"+str(playerAngleFacing))
Print( "Fps:"+str(ScreenFPS() ))
if getpointerstate() then Move_Render_Window()
inc RayCast.Render_Counter
if RayCast.Render_Counter>RayCast.Render_Counter_Max then RayCast.Render_Counter=0
Sync()
GFR=GetFrameTime()
if GetRawLastKey() = 27 then end
Loop
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
function Move_Render_Window()
RayCast.Window_x=getrawmousex()
RayCast.Window_Y=getrawmousey()
x=RayCast.Window_x
for column=1 to RayCast.Columns
setspriteposition(column,x,((RayCast.Resolution_Y/2)+RayCast.Window_Y)-(getspriteheight(column)/2))
inc x,RayCast.Column_Width
next column
setspriteposition(floor,RayCast.Window_x,RayCast.Window_Y+(RayCast.Resolution_Y/2)+16)
setspriteposition(ceiling,RayCast.Window_x,RayCast.Window_Y)
endfunction
//-----------------------------------------------------------------------------------
// this just defines the "world data" (just walls here)
function BuildMap()
map[00] = [ 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2 ]
map[01] = [ 2,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ]
map[02] = [ 1,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2 ]
map[03] = [ 2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,1 ]
map[04] = [ 1,3,4,0,0,0,4,0,0,0,0,0,0,0,4,0,0,0,0,2 ]
map[05] = [ 2,0,3,4,3,4,3,0,0,0,0,0,0,0,3,0,0,0,0,1 ]
map[06] = [ 1,0,4,0,0,0,4,0,0,0,0,0,0,0,4,0,0,0,0,2 ]
map[07] = [ 2,4,3,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,1 ]
map[08] = [ 1,0,0,0,0,0,3,4,3,4,3,4,0,0,4,0,0,0,0,2 ]
map[09] = [ 2,0,4,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,1 ]
map[10] = [ 1,0,3,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,2 ]
map[11] = [ 2,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ]
map[12] = [ 1,0,3,4,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,2 ]
map[13] = [ 2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ]
map[14] = [ 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2 ]
endfunction
//-----------------------------------------------------------------------------------
function RenderMapView()
local wallHalfHeight as float
local rayX as float
local rayZ as float
local stepX as float
local stepZ as float
local mapHit as integer
local distance as integer
local halfViewWidth as integer
local halfViewHeight as integer
// optimizations
local intRayX as integer
local intRayZ as integer
halfViewWidth = (RayCast.Columns / 2)
halfViewHeight = (RayCast.Resolution_Y / 2)
// using 0 as center xpos of view
// so loop runs from -halfViewWidth to halfViewWidth
column=RayCast.Columns // renders from right to left
For i=-halfViewWidth To halfViewWidth - 1
setspritesize(column,RayCast.Column_Width,0)
// start ray a short distance ahead of player
rayX = playerXpos + Sin(playerAngleFacing) / 4
rayZ = playerZpos + Cos(playerAngleFacing) / 4
stepX = Sin(playerAngleFacing + (0.125 * i)) / 32.0
stepZ = Cos(playerAngleFacing + (0.125 * i)) / 32.0
// send out ray for this view column
// remember we send out one ray per column on the view
// the view will often be the size of the screen but of course I may use a smaller view in a corner of the
// screen with stats and other "frames" of information around it for an RPG for example.
// the 500 on the distance comparison here is how far out the "camera" will render the world.
// if the 500 is increased rays will go further out thereby increasing the camera draw distance
// likewise, if the value is decreased rays will go a shorter distance out decreasing the camera draw distance
// Visually what happens is objects outside the range will not be drawn until they are within the range.
// Then they will "pop" into view. I am sure you have seen this in various 3D games over the years.
distance=0
mapHit=0
While mapHit=0 And distance < Raycast.Distance
rayX = rayX + stepX
rayZ = rayZ + stepZ
distance = distance + 1
intRayX = trunc(rayX)
intRayZ = trunc(rayZ)
If map[intRayX,intRayZ] > 0
mapHit = map[intRayX, intRayZ]
EndIf
endwhile
If mapHit <> 0 then Render_Column(i,column,mapHit,distance,wallHalfHeight,halfViewHeight)
dec column
Next
endfunction
//-----------------------------------------------------------------------------------
function Render_Column(i as integer , column as integer,mapHit as integer,distance as integer,wallHalfHeight as integer,halfViewHeight as integer)
local brightness as float
// calculate brightness so walls closer are brighter and walls further away are darker
brightness = 50.0 / distance
If brightness > 1.0 Then brightness = 1.0
if brightness < 0.1 then brightness = 0.1
// get rid of (or at least minimize) the "fish eye" effect for the rendering by applying the cos of the ray angle to the distance
distance = distance * cos((0.125 * i))
// calculate wall height (actually 1/2 of the wall height)
wallHalfHeight = 7000.0 / distance
if wallHalfHeight > halfViewHeight then wallHalfHeight = halfViewHeight
// define the color based on the wall type
select mapHit
case 1 // cyan
RGBred = 0
RGBgreen = 255
RGBblue = 255
endcase
case 2 // darker cyan
RGBred = 0
RGBgreen = 224
RGBblue = 224
endcase
case 3 // blue
RGBred = 0
RGBgreen = 0
RGBblue = 255
endcase
case 4 // darker blue
RGBred = 0
RGBgreen = 0
RGBblue = 224
endcase
case 20 // sprite
endcase
case default: // white
RGBred = 255
RGBgreen = 255
RGBblue = 255
endcase
endselect
Stretch_Column(column , RGBred , RGBgreen , RGBblue , brightness ,wallHalfHeight)
endfunction
//-----------------------------------------------------------------------------------
Function Stretch_Column(column as integer , RGBred as integer , RGBgreen as integer , RGBblue as integer , brightness as float ,wallHalfHeight as integer)
RGBred = RGBred * brightness
RGBgreen = RGBgreen * brightness
RGBblue = RGBblue * brightness
setspritesize(column,RayCast.Column_Width,wallHalfHeight*2)
setspriteposition(column,getspritex(column),((RayCast.Resolution_Y/2)+RayCast.Window_Y)-(getspriteheight(column)/2))
setspritecolor(column,RGBred, RGBgreen, RGBblue,255)
Endfunction
//-----------------------------------------------------------------------------------
function PlayerUpdate()
//move forward if no wall directly ahead
if InputRequestMoveForward()
// save current position for use in HandleSlidingAgainstWall
// that handles wall collisions and is why we don't need to check here
pxold = playerXpos
pzold = playerZpos
//playerXpos = playerXpos + Sin(playerAngleFacing) / (280.0*GFR)
//playerZpos = playerZpos + Cos(playerAngleFacing) / (280.0*GFR)
playerXpos = playerXpos + Sin(playerAngleFacing) / 8
playerZpos = playerZpos + Cos(playerAngleFacing) / 8
HandleSlidingAgainstWall()
endif
//move backwards if no wall directly behind
if InputRequestMoveBackard()
// save current position for use in HandleSlidingAgainstWall
// that handles wall collisions and is why we don't need to check here
pxold=playerXpos
pzold=playerZpos
//playerXpos = playerXpos -Sin(playerAngleFacing) / (280.0*GFR)
//playerZpos = playerZpos -Cos(playerAngleFacing) / (280.0*GFR)
playerXpos = playerXpos -Sin(playerAngleFacing) / 8
playerZpos = playerZpos -Cos(playerAngleFacing) / 8
HandleSlidingAgainstWall()
endif
//turn to the left
if InputRequestTurnLeft()
//playerAngleFacing = playerAngleFacing + (120.0*GFR)
playerAngleFacing = playerAngleFacing + 2.5
if playerAngleFacing > 359 then playerAngleFacing = playerAngleFacing - 360
endif
//turn to the right
if InputRequestTurnRight()
//playerAngleFacing = playerAngleFacing - (120.0*GFR)
playerAngleFacing = playerAngleFacing - 2.5
if playerAngleFacing < 0 then playerAngleFacing = playerAngleFacing + 360
endif
endfunction
//-----------------------------------------------------------------------------------
function HandleSlidingAgainstWall()
if map[trunc(playerXpos), trunc(playerZpos)] <> 0
pxtemp=playerXpos
playerXpos=pxold
If map[trunc(playerXpos), trunc(playerZpos)] <> 0
playerXpos=pxtemp
playerZpos=pzold
If map[trunc(playerXpos), trunc(playerZpos)] <> 0
playerXpos=pxold
EndIf
EndIf
EndIf
endfunction
//-----------------------------------------------------------------------------------
function InputRequestTurnLeft() as integer
result as integer
if GetRawKeyState(KEY_A) then result = 1
if GetRawKeyState(KEY_LEFT) then result = 1
endfunction result
//-----------------------------------------------------------------------------------
function InputRequestTurnRight() as integer
result as integer
if GetRawKeyState(KEY_D) then result = 1
if GetRawKeyState(KEY_RIGHT) then result = 1
endfunction result
//-----------------------------------------------------------------------------------
function InputRequestMoveForward() as integer
result as integer
if GetRawKeyState(KEY_W) then result = 1
if GetRawKeyState(KEY_UP) then result = 1
endfunction result
//-----------------------------------------------------------------------------------
function InputRequestMoveBackard() as integer
result as integer
if GetRawKeyState(KEY_S) then result = 1
if GetRawKeyState(KEY_DOWN) then result = 1
endfunction result
//-----------------------------------------------------------------------------------
function InputRequestShoot() as integer
result as integer
if GetRawKeyPressed(KEY_X) then result = 1
if GetRawKeyPressed(KEY_SPACE) then result = 1
if GetRawKeyPressed(KEY_RSHFT) then result = 1
if GetRawKeyPressed(KEY_ENTER) then result = 1
endfunction result