-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- RayCast Demo! (last updated 01 Jan 2008) -- -- Programmed by Sean Connelly (sean@codetank.com) -- This source code is hereby released as public domain. I reserve no rights. -- -- To run this program, you must load it in Brain Damage, which can be found -- here: http://codetank.com/damage/ -- -- To learn more about Brain Damage, please see http://codetank.com/ -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- This is a highly UNOPTIMIZED raycaster. The demo is meant to simply show -- the raycasting technique quickly, without regards to speed. This code was -- released with Brain Damage v1.2, and needs a minimum of that to run. Use -- the arrow keys to walk around, and enjoy hacking around with the source :-). -- -- If you're feeling adventurous, you can try to make this into a game somehow. -- What I've done in the past is make it a maze game, where the user has to try -- to find the finish. For competition, you might time the user to see how -- long it takes them. -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- config = { width = 500, -- width of window height = 400, -- height of window fov = 60, -- field of view (degrees) sliver_width = 6, -- thickness of each wall sliver (pixels) ray_resolution = 0.02, -- lower number = higher resolution = more accurate = more CPU wall_zoom = 300, -- how much to zoom the walls, play with to see results player_speed = 0.1, -- how fast the player moves forward player_turn = 5, -- how fast the player turns fish_eye = false, -- should the engine remove the "fish eye" side effect? sky_color = rgb(200, 200, 255), floor_color = rgb(63, 63, 0), } -- don't change this stuff config.sliver_width = math.floor(config.sliver_width + 0.5) config.slivers_per_screen = math.ceil(config.width / config.sliver_width) config.ang_per_sliver = config.fov / config.slivers_per_screen config.cy = math.floor(config.height / 2) map = { width = 11, -- width of map height = 11, -- height of map start = {2.5, 2.5}, -- x, y cell to start in startang = 90, -- what direction to face in -- map data... 1-9 are colored walls, 0 is no wall {1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1}, {3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, {4, 0, 0, 0, 0, 0, 7, 8, 7, 0, 3}, {3, 0, 5, 0, 0, 0, 8, 9, 8, 0, 4}, {4, 0, 6, 0, 0, 0, 7, 0, 7, 0, 3}, {3, 0, 5, 0, 0, 0, 8, 0, 8, 0, 4}, {4, 0, 6, 5, 6, 0, 7, 0, 7, 0, 3}, {3, 0, 0, 0, 0, 0, 8, 0, 0, 0, 4}, {4, 0, 6, 0, 3, 0, 7, 8, 7, 0, 3}, {3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, {1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1}, } color = { -- converts a cell number into a wall color rgb( 0, 0, 255), -- cell number 1 is blue rgb( 0, 255, 0), -- cell number 2 is green, etc... rgb( 0, 255, 255), rgb(255, 0, 0), rgb(255, 0, 255), rgb(255, 255, 0), rgb(255, 255, 255), rgb(127, 127, 127), rgb( 0, 0, 0), } player = { x = map.start[1], y = map.start[2], ang = map.startang, } -- gets the wall color of the map, with bounds checking function get_map(x, y) x = math.floor(x) y = math.floor(y) if (x < 1 or y < 1 or x > map.width or y > map.height) then return 1 end return map[y][x] end -- casts a ray starting at (x, y), pointing at ang direction... -- returns the distance the ray travelled until it hit a wall, and the wall's color function cast_ray(x, y, ang) local dist = 0 local col = 1 local dx = math.cos(ang * math.pi / 180) * config.ray_resolution local dy = math.sin(ang * math.pi / 180) * config.ray_resolution repeat x = x + dx -- travel y = y + dy dist = dist + config.ray_resolution -- track distance travelled col = get_map(x, y) if (col ~= 0) then -- is map location still blank? break -- no? so we hit a wall... stop casting end until (false) -- return the distance travelled, and the color of the wall that finally stopped our ray return dist, col end wnd = window.create{ title = "RayCast Demo! " .. _BRAINDAMAGE_VERSION, width = config.width, height = config.height, resize = false, onkeydown = function(wnd, key) local ox = player.x local oy = player.y if (key.key == input.key.UP) then -- move player forward player.x = player.x + math.cos(player.ang * math.pi / 180) * config.player_speed player.y = player.y + math.sin(player.ang * math.pi / 180) * config.player_speed wnd:invalidate(false) elseif (key.key == input.key.DOWN) then -- move player backwards player.x = player.x - math.cos(player.ang * math.pi / 180) * config.player_speed player.y = player.y - math.sin(player.ang * math.pi / 180) * config.player_speed wnd:invalidate(false) elseif (key.key == input.key.RIGHT) then player.ang = (player.ang + config.player_turn) % 360 wnd:invalidate(false) elseif (key.key == input.key.LEFT) then player.ang = (player.ang + 360 - config.player_turn) % 360 wnd:invalidate(false) end -- clip the player to the walls if (get_map(player.x, player.y) ~= 0) then player.x = ox player.y = oy end end, -- disable erasing background, because onpaint handles everything onerasebkgnd = function(wnd, pnt) return false end, onpaint = function(wnd, pnt) local sx = 0 local ang = player.ang - (config.fov / 2) local ray_len local col local height local start_wall -- start from the left side, and cast a ray for each sliver for s = 0, config.slivers_per_screen do -- cast the ray ray_len, col = cast_ray(player.x, player.y, ang) if (not config.fish_eye) then -- remove the fish eye side effect ray_len = ray_len * math.cos((player.ang - ang) * math.pi / 180) end -- calculate height of wall height = math.floor(config.wall_zoom / ray_len) start_wall = math.floor(config.cy - height / 2) -- clip wall to screen if (start_wall < 0) then height = height + start_wall start_wall = 0 else -- wall isn't too high, so draw sky pnt:rectangle(sx, 0, config.sliver_width, start_wall, false, config.sky_color) end if (start_wall + height > config.height) then height = config.height - start_wall else -- wall isn't too long, so draw floor pnt:rectangle(sx, start_wall + height, config.sliver_width, config.height - start_wall - height, false, config.floor_color) end -- draw the wall pnt:rectangle(sx, start_wall, config.sliver_width, height, false, color[col]) -- increase our sliver position sx = sx + config.sliver_width -- increase our sliver angle ang = ang + config.ang_per_sliver end end, } while window.getcount() > 0 do window.pumpmessages() end -- crappy slow raycaster in 200 lines of code :-) (including comments!)