0,]']>S}Wup8d%qy)6o;]AsVl33DDDD333CDDDD3333333CDDD4333DDDD433333333DD"""""""""""""""""-bg"""""""""""mwfgfgfvgfvff8333CD43333DD43DDDDDD33CDDDDD33333CDDDDD4333333DDD"""""""""""""rw"""""""""-rvwfwgfwgf:CDDDD43CDDDDD433DDDDDD4333CDDDDDDD333333DDD""""""""""""""""""""""-rJDD3CDDDDDDD333DDDDDDDD33333DDDD""""""""""""""ҍ""".""J3DDDDDDDDDD333DDDDD""""""/"""""""ݭ"""."":3CDDDDD""""""""""""/"""""""ݝ""""""""""""ҭ""""""""""""-""""""""""""ҭ"""""""""""""""""""""ݍ"""""""""""""""""""""ݭ""""""""""ҭ"-""/"""""""ݝ"-""""/"""""""ݍ""-"""""-""-""""""""""""""""-""""""""""""""""""""""""""ҭ""""""""""""""""""ҭ""""""""""""ҭ""""""""""""""""ݍ""""""""""ݝ""""""""ݭ""""""""""ݍ"""-"""""-""""""-"""""""""""""ݙ""-"""""""""""""ݪ""""""""""""ҭ""""""""ݙ""""""""""""ҭ""""""""݈""""""""ݝ""""""""ݭ`1-- title: Raycast Engine Demo -- author: Wojciech Graj -- desc: Demo of a WolfenStein3D-style raycast engine with variable-height walls. -- script: lua -- input: gamepad Player = { pos_x = 0, pos_y = 0, dir_x = -1, dir_y = 0, plane_x = 0, plane_y = 0.8, } Player.__index = Player function Player.new(pos_x, pos_y) local self = setmetatable({}, Player) self.pos_x = pos_x self.pos_y = pos_y return self end function Player:process(delta) if btn(2) then self:rotate(delta) elseif btn(3) then self:rotate(-delta) end if btn(0) then self:move(delta) elseif btn(1) then self:move(-delta) end end function Player:rotate(delta) local speed = 0.001 * delta --[CONST] local old_dir_x = self.dir_x local math_cos = math.cos local math_sin = math.sin self.dir_x = self.dir_x * math_cos(speed) - self.dir_y * math_sin(speed) self.dir_y = old_dir_x * math_sin(speed) + self.dir_y * math_cos(speed) local old_plane_x = self.plane_x self.plane_x = self.plane_x * math_cos(speed) - self.plane_y * math_sin(speed) self.plane_y = old_plane_x * math_sin(speed) + self.plane_y * math_cos(speed) end function Player:move(delta) local speed = 0.003 * delta --[CONST] local math_floor = math.floor if mget(math_floor(self.pos_x + self.dir_x * speed), math_floor(self.pos_y)) == 0 then self.pos_x = self.pos_x + self.dir_x * speed end if mget(math_floor(self.pos_x), math_floor(self.pos_y + self.dir_y * speed)) == 0 then self.pos_y = self.pos_y + self.dir_y * speed end end Sprite = { pos_x = 0, pos_y = 0, tex_id = 0, scl_horiz = 1, scl_vert = 1, offset_vert = 0, --from 0.5 (floor) to -0.5 (ceiling) screen_offset_vert = 0, dist = 0, --Distance to player (negative if not in viewing triangle) screen_x = 0, screen_width = 0, screen_height = 0, draw_start_y = 0, draw_end_y = 0, draw_start_x = 0, draw_end_x = 0, } Sprite.__index = Sprite function Sprite.new(pos_x, pos_y, tex_id, scl_horiz, scl_vert, offset_vert) local self = setmetatable({}, Sprite) self.pos_x = pos_x self.pos_y = pos_y self.tex_id = tex_id self.scl_horiz = scl_horiz self.scl_vert = scl_vert self.offset_vert = offset_vert return self end function Sprite:process(inv_det) local screen_width = g_SCREEN_WIDTH local screen_height = g_SCREEN_HEIGHT local screen_half_height = screen_height / 2 local player = g_player local math_abs = math.abs local rel_x = self.pos_x - player.pos_x local rel_y = self.pos_y - player.pos_y self.dist = math.sqrt(rel_x * rel_x + rel_y * rel_y) local trans_y = inv_det * (player.plane_x * rel_y - player.plane_y * rel_x) if trans_y <= 0 then self.dist = -self.dist return end local trans_x = inv_det * (player.dir_y * rel_x - player.dir_x * rel_y) self.screen_x = math.floor((screen_width * 0.5) * (1 + trans_x / trans_y)) self.screen_width = math_abs(screen_height // trans_y) // self.scl_horiz self.draw_start_x = self.screen_x - self.screen_width // 2 if self.draw_start_x < 0 then self.draw_start_x = 0 elseif self.draw_start_x >= screen_width then self.dist = -self.dist return end self.draw_end_x = self.screen_x + self.screen_width // 2 if self.draw_end_x >= screen_width then self.draw_end_x = screen_width - 1 elseif self.draw_end_x < 0 then self.dist = -self.dist return end self.screen_height = math_abs(screen_height // trans_y) // self.scl_vert self.screen_offset_vert = screen_half_height * self.offset_vert // trans_y self.draw_start_y = screen_half_height - self.screen_height // 2 + self.screen_offset_vert if self.draw_start_y < 0 then self.draw_start_y = 0 end self.draw_end_y = screen_half_height + self.screen_height // 2 + self.screen_offset_vert if self.draw_end_y >= screen_height then self.draw_end_y = screen_height - 1 end end function get_tex_pixel(offset, id, x, y) return peek4(offset + 0x40 * (id + 16 * (y // 8) + x // 8) + 0x8 * (y % 8) + (x % 8)) end function init() g_SCREEN_WIDTH = 240 g_SCREEN_HEIGHT = 136 g_DEBUG = true g_TEX_WIDTH = 16 g_TEX_HEIGHT = 16 g_SPRITE_SIZES = { [0]={16,16}, [2]={16,16}, } g_TEX_MAP = { [1]=1, [2]=3, [3]=5, [4]=7, [5]=9, } g_player = Player.new(22, 12) g_prev_time = 0 g_sprites = { Sprite.new(12, 13, 0, 2, 2, 0.5), Sprite.new(12.5, 12.5, 0, 2, 2, 0.5), Sprite.new(13, 13, 0, 2, 2, 0.5), Sprite.new(18.5, 6.5, 2, 2, 1, 0.125), } g_settings = { floor_ceil = true, interlace = 2, --disabled=g_interlace>=2 } end init() function TIC() local t = time() local delta = t - g_prev_time --msec since last frame g_prev_time = t local screen_width = g_SCREEN_WIDTH local screen_height = g_SCREEN_HEIGHT local screen_half_height = screen_height // 2 local tex_width = g_TEX_WIDTH local tex_height = g_TEX_HEIGHT local sprite_sizes = g_SPRITE_SIZES local tex_map = g_TEX_MAP local player = g_player local sprites = g_sprites local settings = g_settings local math_floor = math.floor local math_abs = math.abs local start_vline local step_vline if settings.interlace >= 2 then start_vline = 0 step_vline = 1 cls(0) else start_vline = (settings.interlace + 1) % 2 settings.interlace = start_vline step_vline = 2 for x=start_vline,screen_width-1,step_vline do for y=0,screen_height-1 do pix(x, y, 0) end end end -- game logic t = time() player:process(delta) if btnp(4) then if settings.interlace >= 2 then settings.interlace = 0 else settings.interlace = 2 end end if btnp(5) then settings.floor_ceil = not settings.floor_ceil end local t_temp = time() local t_logic = t_temp - t t = t_temp -- drawing local inv_det = 1 / (player.plane_x * player.dir_y - player.dir_x * player.plane_y) local visible_sprites = {} for key,sprite in pairs(sprites) do sprite:process(inv_det) if sprite.dist > 0 then visible_sprites[#visible_sprites+1] = sprite end end table.sort(visible_sprites, function(a,b) return a.dist < b.dist end) local num_visible_sprites = #visible_sprites -- draw walls and sprites for x=start_vline,screen_width-1,step_vline do local camera_x = 2 * x / screen_width - 1 local ray_dir_x = player.dir_x + player.plane_x * camera_x local ray_dir_y = player.dir_y + player.plane_y * camera_x local map_x = math_floor(player.pos_x) local map_y = math_floor(player.pos_y) local delta_dist_x = math_abs(1 / ray_dir_x) local delta_dist_y = math_abs(1 / ray_dir_y) local step_x local side_dist_x if ray_dir_x < 0 then step_x = -1 side_dist_x = (player.pos_x - map_x) * delta_dist_x else step_x = 1 side_dist_x = (map_x + 1.0 - player.pos_x) * delta_dist_x end local step_y local side_dist_y if ray_dir_y < 0 then step_y = -1 side_dist_y = (player.pos_y - map_y) * delta_dist_y else step_y = 1 side_dist_y = (map_y + 1.0 - player.pos_y) * delta_dist_y end local current_sprite = 1 local not_hit_full_wall = true local prev_draw_start = screen_height while not_hit_full_wall do -- Get next wall tile using DDA local side local tile_data local not_hit = true while not_hit do if side_dist_x < side_dist_y then side_dist_x = side_dist_x + delta_dist_x map_x = map_x + step_x side = 0 else side_dist_y = side_dist_y + delta_dist_y map_y = map_y + step_y side = 1 end tile_data = mget(map_x, map_y) if tile_data > 0 then not_hit = false end end local perp_wall_dist if side == 0 then perp_wall_dist = (map_x - player.pos_x + (1 - step_x) * 0.5) / ray_dir_x else perp_wall_dist = (map_y - player.pos_y + (1 - step_y) * 0.5) / ray_dir_y end --draw sprites for sprite_idx=current_sprite,num_visible_sprites do local sprite = visible_sprites[sprite_idx] if sprite.dist >= perp_wall_dist then break end current_sprite = sprite_idx + 1 if x >= sprite.draw_start_x and x <= sprite.draw_end_x then local sprite_size = sprite_sizes[sprite.tex_id] local a = sprite_size[2] / sprite.screen_height local sprite_tex_x = math_floor((x - (sprite.screen_x - sprite.screen_width / 2)) * sprite_size[1] / sprite.screen_width) % sprite_size[1] for y=sprite.draw_start_y,sprite.draw_end_y do local tex_y = math_floor((y - sprite.screen_offset_vert - screen_half_height + sprite.screen_height / 2) * a) % sprite_size[2] local color = get_tex_pixel(0xC000, sprite.tex_id, sprite_tex_x, tex_y) if color > 0 and pix(x, y) == 0 then pix(x, y, color) end end end end --draw wall local tile_height = tile_data // 16 / 16 -- 0=full height, 1=no height if tile_height == 0 then not_hit_full_wall = false end local line_height = screen_height // perp_wall_dist local draw_start = screen_half_height + math_floor(line_height * (tile_height - 0.5)) + 1 if draw_start < 0 then draw_start = 0 elseif draw_start >= screen_height then draw_start = screen_height - 1 end local draw_end = screen_half_height + line_height // 2 - 1 if draw_end > prev_draw_start then draw_end = prev_draw_start elseif draw_end >= screen_height then draw_end = screen_height - 1 end local wall_x if side == 0 then wall_x = player.pos_y + perp_wall_dist * ray_dir_y else wall_x = player.pos_x + perp_wall_dist * ray_dir_x end wall_x = wall_x - math_floor(wall_x) local tex_id = tex_map[tile_data % 16] local tex_x = math_floor(wall_x * tex_width) local step_tex = tex_height / line_height local testart_vline = (draw_start - screen_half_height + line_height * 0.5) * step_tex for y=draw_start,draw_end do if pix(x, y) == 0 then local tex_y = math_floor(testart_vline + step_tex * (y - draw_start)) % tex_height pix(x, y, get_tex_pixel(0x8000, tex_id, tex_x, tex_y)) end end --draw top of variable-height walls if tile_height > 0.5 then if side_dist_x < side_dist_y then perp_wall_dist = (map_x + step_x - player.pos_x + (1 - step_x) * 0.5) / ray_dir_x else perp_wall_dist = (map_y + step_y - player.pos_y + (1 - step_y) * 0.5) / ray_dir_y end line_height = screen_height // perp_wall_dist local top_draw_start = screen_half_height + math_floor(line_height * (tile_height - 0.5)) if top_draw_start >= screen_height then top_draw_start = screen_height - 1 end local row_distance_part = (2 * tile_height - 1) * screen_half_height for y=top_draw_start,draw_start - 1 do if pix(x, y) == 0 then local row_distance = row_distance_part / (y - screen_half_height) local floor_x = player.pos_x + row_distance * ray_dir_x local floor_y = player.pos_y + row_distance * ray_dir_y local tex_x = math_floor(tex_width * floor_x) % tex_width local tex_y = math_floor(tex_height * floor_y) % tex_height pix(x, y, get_tex_pixel(0x8000, tex_id, tex_x, tex_y)) end end prev_draw_start = top_draw_start else prev_draw_start = draw_start end end end t_temp = time() local t_wall_sprite = t_temp - t t = t_temp --draw floor + ceiling if settings.floor_ceil then local ray_dir_x0 = player.dir_x - player.plane_x local ray_dir_y0 = player.dir_y - player.plane_y local ray_dir_x1 = player.dir_x + player.plane_x local ray_dir_y1 = player.dir_y + player.plane_y for y=screen_half_height,screen_height do local row_distance = screen_half_height / (y - screen_half_height) local floor_step_x = row_distance * (ray_dir_x1 - ray_dir_x0) / screen_width local floor_step_y = row_distance * (ray_dir_y1 - ray_dir_y0) / screen_width local floor_x = player.pos_x + row_distance * ray_dir_x0 + start_vline * floor_step_x local floor_y = player.pos_y + row_distance * ray_dir_y0 + start_vline * floor_step_y floor_step_x = floor_step_x * step_vline floor_step_y = floor_step_y * step_vline for x=start_vline,screen_width-1,step_vline do local tex_x = math_floor(tex_width * floor_x) % tex_width local tex_y = math_floor(tex_height * floor_y) % tex_height -- draw floor if pix(x, y) == 0 then pix(x, y, get_tex_pixel(0x8000, 3, tex_x, tex_y)) --[CONST] end --draw ceiling if pix(x, screen_height - y - 1) == 0 then pix(x, screen_height - y - 1, get_tex_pixel(0x8000, 5, tex_x, tex_y)) --[CONST] end floor_x = floor_x + floor_step_x floor_y = floor_y + floor_step_y end end end t_temp = time() local t_floor = t_temp - t if g_DEBUG then print(string.format("FPS %d\n#SPR %d\nLOGIC %.1f\nWALL&SPR %.1f\nFLR&CEIL %.1f", math_floor(1000 / delta), num_visible_sprites, t_logic, t_wall_sprite, t_floor), 0, 0, 5) end end